Skip to content

Management API

The Management API is opencodex’s control plane. The dashboard at http://localhost:10100 is one client of it; headless ocx provider, model, combo, account, settings, diagnostics, and lifecycle commands are clients too. The API is available only while the proxy is running.

Use the Web Dashboard for an interactive client, or this reference when building automation. Persistent values ultimately follow Configuration.

The Management API has its own admin credential, independent of data-plane API keys. At startup, opencodex resolves it in this order:

  1. OPENCODEX_ADMIN_AUTH_TOKEN, when set.
  2. A generated ocx_admin_* token in a hardened secret file.

The file-backed token is accepted only after its directory and file permissions or ACLs have been hardened. If that cannot be guaranteed, management authentication fails closed and the API returns 503 until an environment token is supplied or the file state is repaired.

Send the admin token in either form:

X-OpenCodex-API-Key: <admin-token>
Authorization: Bearer <admin-token>

On a loopback bind, the dashboard bootstrap can receive a short-lived ocx_session_* credential. Each session lasts five minutes and is bound to the exact dashboard origin. Safe requests must match that origin. Unsafe methods also require the browser Origin and the session’s CSRF token.

Session issuance is disabled whenever data-plane authentication is required, which includes remote binds. A remote operator must authenticate with the raw admin token; no loopback-style GUI session is minted.

All endpoint rows below inherit these boundary errors. The “Notable errors” column lists additional route-specific results rather than repeating this table.

Status Type or code Meaning
401 opencodex admin token required The admin token or GUI session is missing, invalid, expired, origin-mismatched, or missing CSRF evidence
403 cross-origin request blocked The request origin is outside the management allowlist
404 not_found No management route matched the method and path
413 request body too large A POST, PUT, or PATCH body exceeds the 2 MiB management limit
503 management API unavailable Admin credential initialization or hardening is unavailable
503 oauth_mutation_busy Another OAuth credential mutation holds the writer; response includes Retry-After: 1
503 catalog_busy Catalog gathering is already at capacity; response includes Retry-After: 1
Method and path Purpose Notable errors
GET, PUT /api/v2 Read or change native multi-agent v2 mode and thread settings 400 invalid settings; 502 transition or persistence failure
GET, PUT /api/injection-model Read or set the injected sub-agent model, effort, prompt, and guidance settings 400 invalid model, effort, or body
GET, PUT /api/effort-caps Read or set global and sub-agent reasoning-effort ceilings 400 invalid ladder value
GET, PUT /api/subagent-models Read or order the models advertised to sub-agents 400 invalid list or more than five models
GET, PUT /api/subagent-model-fallback Read or set the ordered fallback chain and poll interval 400 invalid list or poll interval
GET /api/grok Read Grok managed-config status and candidate models 400 status read failure
PUT /api/grok/selection Persist the excluded Grok models 400 invalid or oversized selection
POST /api/grok/apply Apply persisted Grok configuration through the managed sync 409 grok_apply_busy; 400/500 apply failure
GET /api/grok/reset-coupons?accountId=... Read remaining Grok billing reset tokens and validity windows for the active or specified xAI account 400 missing account; 401 unauthenticated; 502 upstream gRPC-Web error
POST /api/grok/reset-coupons/consume Redeem an eligible reset coupon. Body { accountId?, tokenId?, operationId? }. Optional operationId (UUIDv4) makes redemption idempotent: repeating the same ID replays the durable result without double-redemption. 400 invalid JSON/UUID; 401 unauthenticated; 409 identity_mismatch; 502 upstream error; 503 ledger capacity
GET, PUT /api/claude-desktop Read or persist the Claude Desktop routed/native profile 400 invalid or unavailable assignment
POST /api/claude-desktop/apply Write the saved profile to Claude Desktop’s managed config 400/500 write failure
GET /api/claude-desktop/status Inspect saved-versus-applied profile and Desktop health 400 status read failure
GET, PUT /api/claude-code Read or update Claude Code gateway, auth-mode, model-map, context, agent, and sidecar settings 400 invalid field or shape

The dashboard drives both coupon paths from Providers > xAI Grok > Accounts: each signed-in account row carries a ticket badge with its remaining coupon count, and the badge opens a dialog that lists validity windows and redeems the coupon closest to expiry. The dialog sends a client-minted operationId, and it stops sending after a timeout instead of retrying, because a redemption whose journal record is still open would execute again. ocx account grok-reset-coupons remains the terminal equivalent.

For the concepts behind the model roster and encrypted worker-task behavior, see Sub-agent Surface.

Method and path Purpose Notable errors
GET /api/client-integrations/journal?client=... List rollback operations, optionally for one client. Each row includes the server-computed deletable flag. 400 invalid client
DELETE /api/client-integrations/journal?opId=... Retire one older rollback operation and remove its snapshot when possible. Success returns snapshotRemoved; false means cleanup was retained for maintenance retry. 400 missing opId; 404 missing or already retired operation; 409 newest operation for that client

Deletion appends a tombstone instead of rewriting the journal. The newest operation for each client is protected server-side so the current undo point remains available. For Aside, protection is per profile, and journal rows include profileId.

Use these dedicated paths with a compatible running proxy. {profileId} is a registered nonnegative integer account ID returned by the profile list; it is not a browser path.

Method and path Purpose Notable errors
GET /api/client-integrations/aside/profiles List profiles[], desired enabledCount/allEnabled, actual appliedCount, and total HTTP 200 may contain an empty, unsafe aggregate with an error when discovery is unavailable
PUT /api/client-integrations/aside/profiles Set every registered profile’s desired state and apply it; body { "enabled": true }, with optional overwriteConflict when enabling 400 invalid body; 409 operation busy; 500 preference-save failure; 207 per-profile refusals
GET /api/client-integrations/aside/profiles/{profileId} Read one profile’s desired enabled and actual integration status 400 invalid ID; 404 unregistered profile
PUT /api/client-integrations/aside/profiles/{profileId} Change one profile with the same body as bulk PUT, leaving sibling preferences unchanged 400 invalid body/ID; 404 unknown profile; 409 busy or refusal; 500 save/write failure
GET /api/client-integrations/aside/profiles/journal List history across registered Aside profiles Rows include profileId, snapshot availability, undoable, and deletable
GET /api/client-integrations/aside/profiles/{profileId}/journal List one profile’s history, including matching legacy operations 400 invalid ID; 404 unknown profile
DELETE /api/client-integrations/aside/profiles/journal?opId=... Retire an older operation, resolving its profile from history 400 missing ID; 404 missing operation; 409 newest operation for its profile
DELETE /api/client-integrations/aside/profiles/{profileId}/journal?opId=... Retire an older operation belonging to the selected profile Same deletion errors; an operation cannot target a different profile
POST /api/client-integrations/aside/profiles/{profileId}/restore Undo an operation using { "opId": "..." }; optional confirmDrift: true permits replacing later edits 404 missing operation/profile; 409 busy, mismatch, or required drift confirmation; 410 expired snapshot; 500 save/write failure
POST /api/client-integrations/aside/sync Refresh enabled profiles through the server’s mutation owner; body {} 400 nonempty body/profile selector; 409 busy; 207 per-profile refusals

Bulk PUT returns { ok, clientId, changed, state, message, results }; each result identifies its profileId and reports the writer outcome. Sync returns { ok, clientId, results }, with per-profile refresh outcomes. Both return HTTP 200 when all returned attempts succeed and HTTP 207 with ok: false when any attempt refuses. HTTP 207 is a partial-result envelope, including when every attempted profile refuses: inspect each result rather than treating a 2xx response as complete success. A successful no-op can have changed: false; sync does not attempt disabled profiles. Single-profile writes and restores return HTTP 200 on success or the corresponding error status on refusal.

Explicit changes save desired preferences before writing files. A preference-save failure leaves profile files unchanged. A later file refusal preserves saved intent and successful sibling writes; inspect the affected profile before retrying. Refusals may include snapshotPath and residual: true when recovery did not finish. Restore also reconciles the target profile’s desired state, so the next sync does not reverse Undo. Deletion returns snapshotRemoved; false means snapshot cleanup still needs maintenance.

The legacy GET, PUT /api/client-integrations/aside aliases remain available. New clients should use the dedicated paths above so an older proxy cannot ignore a profile selector. See Aside profile controls for CLI commands and the proxy upgrade, restart, and retry sequence.

Requires Hub mode and OCX_REMOTE_WORKSPACE_ENABLED=1 on the Hub process. Disabled status is readable; mutations refuse without initializing workspace services.

Method and path Purpose Notable errors
GET /api/remote-workspace Read paired computers, current capabilities, Hub runtimes, and session snapshots Disabled status when Hub role or explicit opt-in is absent
POST /api/remote-workspace/pairing Create a ten-minute one-use Executor enrollment code GUI session only; 429 pairing capacity
GET /api/remote-workspace/runtimes Read Codex, Claude Code, and Pi availability on the Hub
GET, POST /api/remote-workspace/sessions List sessions or start one bound to a device, root, runtime, and access mode POST is GUI session only; 409 offline/unavailable/invalid target
POST /api/remote-workspace/sessions/{id}/prompt Continue the bound model session GUI session only; 409 active turn, offline Executor, or resume failure
DELETE /api/remote-workspace/sessions/{id} Stop the model runtime and encrypted Executor session GUI session only; 404 unknown session
DELETE /api/remote-workspace/devices/{id} Revoke one computer and stop its sessions GUI session only; 404 unknown device

Executor enrollment exchanges a one-use code at POST /remote-workspace/pair and then opens /remote-workspace/agent as a bearer-authenticated outbound WebSocket. Those two machine endpoints are not general management API authority. The bearer is device-scoped, and each work session adds a signed E2EE handshake. Ten failed pairing codes from one kernel-observed peer return 429 with Retry-After for the remainder of the fixed ten-minute window. Tailscale Serve clients share the management listener’s loopback peer bucket; the identity header is not used for throttling because a direct local process could forge it. See Remote Workspace for the end-user flow and trust boundaries.

Session snapshots include resumable. It becomes true only after the selected coding-agent runtime has durable history; notably, a new Claude Code session remains false until its first prompt completes.

Method and path Purpose Notable errors
GET /api/combos List normalized combos and their public model ids Catalog work can return catalog_busy
PUT /api/combos Create, replace, or rename one combo 400 invalid id, target, config, rename, or ordinary collision; 409 Codex-account namespace collision
DELETE /api/combos?id=... Delete one combo and clear its selection/cooldown state 400 missing id; 404 unknown combo

See Combos for target strategies, cooldowns, aliases, and routing failures.

Method and path Purpose Notable errors
GET /api/config Return the redacted, management-safe configuration DTO
PUT /api/config Disabled full-config replacement guard 405; use focused endpoints instead
GET, PUT /api/settings Read runtime/startup settings or update auto-start, stream mode, app-owned memory budget, and codexAccountPickerEnabled 400 invalid, non-object, or empty update
GET /api/startup-health Read cached service/shim startup health
POST /api/startup-action Install or repair the service or Codex shim 400 invalid action; 500 action failure
GET, POST /api/windows-tray Read Windows tray state or install/start/stop/uninstall it 400 unsupported platform/action; 500 operation failure
GET /api/diagnostics/project-config Read cached project configuration warnings
POST /api/sync Sync the current model catalog into Codex 500 failed sync
GET /api/update/check Check the latest or preview update channel 400 invalid tag
POST /api/update/run Start an update job, optionally followed by restart 400 invalid body; job-specific conflict/error status
GET /api/update/status Poll an update job by id 404 unknown job
GET, PUT /api/sidecar-settings Read or update web-search and vision sidecar model/backend settings 400 invalid shape, backend, or limit
GET, PUT /api/shadow-call-settings Read or update shadow-call interception settings 400 invalid shape or value

GET /api/logs accepts an optional opaque cursor from its previous response. The envelope preserves logs, total, generatedAt and timeZone, and adds cursor and reset. Without a cursor it returns the full filtered window. A valid unchanged prefix returns only appended rows; reset: true replaces the client window after edits, eviction, query changes or restart. Invalid cursors return HTTP 400 with error.code: "invalid_cursor". Authentication is unchanged. The dashboard falls back to full snapshots for older servers. This reduces response bytes for stable windows; server projection remains bounded by the current window size.

Method and path Purpose Notable errors
GET /api/logs Query filtered in-memory request logs
GET, PUT /api/debug Read debug flags; set, clear, or reset capture categories 400 invalid or empty update
GET /api/debug/logs Read bounded provider/debug log entries
GET /api/debug/usage-logs Read bounded usage-debug entries
GET /api/debug/injection-logs Read bounded guidance-injection debug entries
GET /api/claude/inbound-debug Read Claude inbound debug state and entries
GET /api/usage Scan the usage ledger into compact aggregates of readable rows, then incrementally fold verified appends; summarize by preset or inclusive custom window and client surface, with a Codex accounts breakdown keyed by stable non-PII log labels 400 invalid custom bounds; returns an error: "read_failed" summary if storage cannot be read
GET /api/storage Scan Codex storage usage by bucket Returns an error: "scan_failed" payload on scan failure
POST /api/storage/cleanup/preview Preview archived-session cleanup and return a binding digest 400 invalid_json or invalid_percent
POST /api/storage/cleanup Quarantine or permanently remove the previewed archived set 400 invalid input; 409 stale/busy/referenced state; 500 filesystem/database failure
GET /api/storage/trash List quarantined cleanup entries 500 trash_list_failed
POST /api/storage/trash/restore Restore one quarantined entry 400 invalid id; 404 missing trash; 409 busy/destination conflict; 500 restore failure
GET /api/storage/trash/restore/test-stream Test-only restore stream hook 404 not_available when test hooks are off
GET, PUT /api/storage/cleanup-policy Read or update scheduled cleanup policy and job state 400 invalid policy
POST /api/storage/cleanup-policy/run Start a manual cleanup-policy run 409 already_running; 500 cleanup_failed
GET /api/storage/cleanup-policy/test-stream Test-only policy stream hook 404 not_found when unavailable

If a scanned row exceeds the existing parser size limit, GET /api/usage and GET /api/keys keep the readable-row aggregates and add usageIncomplete: true with usageIncompleteReason: "oversized_rows" at response level. This diagnostic survives cached responses and incremental appends, including empty or unmatched results; a rebuild recalculates it. No provider, model, or API-key identifier is shortened to make a row fit. An absent flag is not proof that every ledger record was valid. This is separate from historyTruncated, entriesTruncated, and token measurement coverage.

New xAI attempts in usage.jsonl include a request-time credentialSource: grok-oauth for the resolved Grok CLI OAuth transport, or xai-api-key for the public xAI API key transport. This fixed label contains no credential or account identifier. It belongs to each item in attempts, so a combo’s aggregate token total must not be attributed to its final provider. Custom destinations and historic rows omit the field; consumers must not infer subscription usage from the current configuration, model name, or inbound API key. The log reports usage, not subscription invoice amounts.

API-key attempts also record accountLogLabel as k followed by 32 lowercase hex digits. The label is the first 128 bits of SHA-256 over JSON.stringify(["ocx-key-account-v1", providerName, entryId ?? null, reference]). The reference is the configured key value captured for the physical request, before environment or keychain resolution. Raw keys, references, and pool IDs are not written to the label field. A consumer can derive the same label from its local configuration without resolving secrets. Changing a literal key or reference changes the label; replacing the secret behind an unchanged reference keeps the same logical account. Older unlabeled records cannot be attributed reliably.

Key selection is recorded after queued requests have been rebuilt for the current selection. When a retry changes keys, attempts retains a separate record for the preceding key, including reported usage from failed responses. Missing usage remains unreported. Routed adapter terminals are observed before image/search loops or continuation guards combine their usage. Consumers sum the flat attempts by provider/account and do not add the parent combo total again. These records identify usage; provider quota percentages remain separate upstream observations.

GET /api/usage reads ~/.opencodex/usage.jsonl from the beginning through the current ledger snapshot on a cold start. It processes fixed 1 MiB chunks and retains compact aggregate state rather than every normalized request row. Later refreshes validate the previous line boundary and fold only newly appended complete rows. Concurrent callers share the same refresh. Range and surface predicates are applied to the readable-row aggregate, so the former read-byte window and parsed-row cap cannot omit an earlier file prefix from 7-day, 30-day, or all-history totals. managementUsageMaxReadBytes remains accepted for compatibility with bounded legacy readers, but changing it no longer expands or reduces the history summarized by this endpoint.

Pass both since and until to select an inclusive custom interval. Each accepts integer Unix epoch milliseconds, or a full ISO datetime with an explicit timezone. Invalid dates, negative or out-of-range values, reversed bounds, and a single bound are rejected. Custom bounds override range; the response keeps the preset range field for compatibility and adds customWindow: true, the exact since, and until. generatedAt remains the time the report was produced.

Custom windows filter individual ledger entries before daily aggregation, including partial first and last days. They preserve surface, provider, model, and apiKeyId filtering and never reuse or overwrite unfiltered preset summaries. The daily chart remains capped at 366 local calendar days; totals cover the full requested interval. Snapshot-window fields describe the scanned ledger before the time filter, so they can extend beyond the requested bounds.

The Usage page accepts local date/time inputs. Its selected ending minute includes the entire minute through :59.999. Choosing a preset or clearing the custom window restores preset behavior. This adds exact range selection and existing cost estimates; it does not add hourly chart buckets or offline reporting.

The runtime ledger is append-only. Replacing or truncating it, or changing local pricing/time-zone inputs, triggers a complete rebuild. If you manually edit an older row in place while the proxy is running, restart the proxy (or replace the file) before relying on the new total; incremental refreshes verify the append boundary, not every previously aggregated byte.

The response still includes historyTruncated, truncatedPrefixBytes, entriesTruncated, and entriesDropped so older clients can consume the same wire shape. A successful whole-ledger scan reports false, 0, false, and 0, respectively. These are legacy compatibility fields, not a signal that the endpoint read only a configured-size tail.

For GET /api/usage?range=30d&surface=codex, accounts contains one row per observed Codex pool label. Each row reports accountLogLabel, token totals, usageCoverageRatio, and an optional estimatedCostUsd based on the currently configured display pricing. Active user modelCosts overlays take priority over bundled verified catalog and price fallbacks, and historical usage is re-estimated from the pricing active when the summary is read. This is an API-equivalent estimate, not a subscription charge. New main-pool requests use the reserved main label; legacy bare openai rows remain in an ambiguous bucket instead of being reassigned from current configuration.

Manual model prices can also be edited from Models → Price. A manual-pricing badge survives catalog reloads. Prices are stored in providers.<name>.modelCosts and survive catalog sync. Explicit all-zero user rates mean a known-zero estimate; Reset to automatic removes the override and restores the usual catalog fallback. These remain display estimates, not bills.

GET /api/providers/{provider}/model-costs returns { provider, modelCosts }, with sanitized four-rate entries keyed by exact upstream model ID. PUT on the same route accepts { modelId, cost }, where cost is { input, output, cacheRead, cacheWrite } or null to reset. All four rates must be finite numbers from 0 through 1,000,000, in USD per 1M tokens. Unknown fields and malformed rates are rejected. A write preserves other models’ overrides and returns { ok: true, provider, modelId, cost }; reset returns cost: null.

Terminal window
ocx models price ollama/custom-model --json
ocx models set-price ollama/custom-model --input 0.50 --output 1.50
ocx models set-price ollama/custom-model --input 0 --output 0
ocx models set-price ollama/custom-model --auto

Omitted CLI cache-read/cache-write rates default to zero. Use --cache-read and --cache-write to set them explicitly. A provider name remains an exact configuration identity; account display labels are not editable provider names.

Rows in models, providers, and days[].models also carry cacheHitRate: the share of input tokens served from the provider’s prompt cache, clamped to [0, 1]. It is null — never 0 — when the provider reported no cache telemetry or the row has no input tokens, because “no cache data” and “a genuine 0% hit rate” are different facts and a chart that renders them alike is misleading.

Cleanup recovery manifests are published atomically, preserving the previous complete record if a replacement fails before publication. This does not reverse a permanent purge: restore can still fail when a recorded session has no surviving rollout file.

Method and path Purpose Notable errors
GET /api/catalog Return the installed Codex catalog document. Remote clients should prefer the data-plane GET /v1/catalog, which still requires an ordinary data-plane credential but not an admin token. 404 catalog not found
GET /api/models Return the dashboard/CLI model rows catalog_busy when gathering is saturated
GET /api/client-config?client=... Build a read-only client config for any supported file integration 400 unsupported client; 503 catalog unavailable
PUT /api/disabled-models Replace the shared disabled-model list 400 invalid JSON
PUT /api/model-visibility Atomically change provider- or model-level visibility 400 invalid provider, scope, target, or body; 409 initial_model_selection_pending (refresh the model list and retry)
GET, POST /api/custom-models List custom models or add one 400 invalid fields; 404 provider missing; 409 duplicate model
PUT, DELETE /api/custom-models/{id} Edit or delete one custom model 400 invalid id/fields; 404 not found; 409 duplicate model
GET, PUT /api/selected-models Read provider allowlists and availability, or replace one allowlist 400 missing provider/body; 404 unknown provider; PUT 409 initial_model_selection_pending
GET, PUT /api/model-presets Read preset summaries or choose preset/all/custom mode 400 invalid mode or unsupported preset; 404 unknown provider; PUT 409 initial_model_selection_pending

A manual model replaces the Models dashboard row with the same provider and model ID. For OpenAI, the manual row keeps openai/<model> and supports the same visibility controls as other routed models; removing it restores the bare native dashboard row. Explicit account-qualified native rows stay separate. This does not rename bare native routes or change account entitlements. Non-native OpenAI visibility targets must match a configured manual model.

Valid PUT requests to /api/selected-models and /api/model-presets return HTTP 409 with code initial_model_selection_pending until a reliable initial model list is available. Refresh model discovery (for example, GET /api/models) and retry after it succeeds.

Successful visibility/selection writes to /api/disabled-models, /api/model-visibility, /api/selected-models, and /api/model-presets report follow-up outcomes in catalogRefresh and clientIntegrations when that refresh path runs. HTTP 200 and ok: true confirm the selection save; they do not guarantee every client catalog updated. Inspect clientIntegrations[] for ok: false, client, optional Aside profileId, and the refusal reason; recovery details may also include refusalReason, snapshotPath, and residual. The Models page keeps the saved selection and shows a separate client-refresh warning. Inspect Integrations and resolve the reported issue before retrying ocx sync. Missing outcome fields from an older server do not establish successful recovery.

OAuth accounts, provider keys, and data-plane keys

Section titled “OAuth accounts, provider keys, and data-plane keys”
Method and path Purpose Notable errors
GET /api/oauth/providers List providers with public OAuth login flows
GET /api/key-providers List providers configured through API-key login
POST /api/oauth/login Start an OAuth login or account-add flow 400 unknown/invalid provider; oauth_mutation_busy
POST /api/oauth/login/code Submit a manual callback URL or authorization code 400 invalid provider/code; oauth_mutation_busy
POST /api/oauth/login/cancel Cancel a public in-progress OAuth flow 400 unknown provider
GET /api/oauth/status Poll one provider’s OAuth flow 400 unknown provider
POST /api/oauth/logout Remove the selected provider credential 400 unknown provider; oauth_mutation_busy
GET, DELETE /api/oauth/accounts List masked accounts or remove one account 400 invalid provider/id; 404 account missing; oauth_mutation_busy
PUT /api/oauth/accounts/active Select the active OAuth account 400 invalid provider/account; oauth_mutation_busy
GET, PUT, PATCH /api/pool/settings Read or update pool policy for any kind (codex, anthropic, generic); answers with the same keys for all three and declares in supported which the kind honours 400 unknown provider, a field the kind does not support, or an invalid value
GET, PUT, PATCH /api/oauth/accounts/pool Legacy per-pool policy for Anthropic and generic OAuth providers; superseded by /api/pool/settings and kept for existing clients 400 codex or api-key provider, or invalid policy
POST /api/oauth/accounts/clear-cooldown Clear one OAuth account’s runtime cooldown 400 invalid provider/account
PUT /api/oauth/accounts/alias Set or clear an OAuth account alias 400 invalid provider/account/alias
GET, POST, DELETE /api/providers/keys List masked provider keys, add/activate one, or remove one 400 invalid input; 404 provider/key missing
PUT /api/providers/keys/active Select a provider’s active key 400 invalid input; 404 provider/key missing
PUT /api/providers/keys/alias Set or clear a provider-key alias 400 invalid input; 404 provider/key missing
GET, POST, PATCH, DELETE /api/keys List, create, edit, or delete data-plane admission keys 400 invalid body/id; 404 key missing

Credential list responses are deliberately masked. OAuth access tokens and complete provider API keys are not returned to dashboard clients.

Method and path Purpose Notable errors
GET /api/providers List redacted provider configuration and discovery state
POST /api/providers Add or replace one validated provider and optionally make it default 400 invalid/dangerous destination or config; 409 namespace collision
PATCH /api/providers?name=... Update allowed provider fields (including a merged headers block), enabled/default state, or OpenAI account mode 400 invalid field or transition; 404 unknown provider
DELETE /api/providers?name=... Delete a provider, reassigning the default when possible 404 unknown provider; 409 last_provider; 409 provider_has_dependent_combos
POST /api/providers/test?name=... Perform a bounded live provider connectivity/model-discovery probe 404 unknown provider; failures are normally returned as ok: false evidence
GET /api/provider-quotas Read provider quota reports; refresh=1 forces refresh
GET /api/quota-resets List recently detected quota-window resets and whether detection is enabled; limit=<n> caps the count 400 invalid limit
GET, PUT /api/provider-context-caps Read or update global, all-provider, or one-provider context caps 400 invalid request; 404 unknown provider
GET /api/provider-presets Return GUI provider presets derived from the runtime registry

The provider context-cap response includes caps (active limits) and values (last selected values, retained while disabled). Enabling a provider without value restores its selection, or uses the global contextCapValue on first enable. This also applies to OpenAI: the switch does not select a special 922k mode. An active cap bounds every native window; models with a supported long-context window may expand only up to their own supported ceiling. Updating the global value with { "value": 600000, "setAll": true } changes only enabled provider caps; disabled providers keep their remembered selections when later enabled. In contrast, { "setAll": true } without value enables every configured provider at the current global value, replacing their remembered selections. Turning a cap off does not activate its remembered value or erase the selection.

provider_has_dependent_combos is a safety barrier: remove or edit the dependent combos before deleting their provider.

Method and path Purpose Notable errors
GET /api/github/star Read repository star status through the user’s gh session Status-specific fixed result codes
POST /api/github/star Star the repository only from an authenticated human action 403 agent_consent_required for agent-driven callers without dashboard-session evidence
GET /api/update/badge Read the cheap sidebar update-badge state
Method and path Purpose Notable errors
GET /api/system/memory Return scalar process, heap, stream, response-state, watchdog, and active-turn metrics. Response-state diagnostics include spill-write status, consecutive failures, fixed privacy-safe failure class, and last failure/success timestamps. spillLastWriteFailureOrigin is retry_returned_timeout, timeout_memo_refusal, or null; cumulative spillAclRetryReturnedTimeouts and spillAclTimeoutMemoRefusals count terminal failed publications. See Windows spill diagnostics for process-local semantics. Raw errors and paths are never returned.
POST /api/system/restart Begin a drain-aware process restart without removing client injection Returns 202; repeated calls report the existing drain
POST /api/stop Stop the service, restore native Codex, remove managed Grok injection, and drain the proxy 409 service ownership conflict; 409 respawnable_service when a Windows Task Scheduler wrapper could respawn the proxy and the caller is not ocx stop (nothing is changed); 409 self_unload_service when this proxy is running as the installed launchd/systemd service, because stopping the manager from inside it would end the process before native Codex is restored — run ocx stop instead (nothing is changed); 409 when the installed manager refuses to stop; 409 service_state_unknown when the Task Scheduler state cannot be read (nothing is changed; repair the query and retry)
GET /api/system/codex-app-server Report whether running Codex app-servers predate the current model catalog
POST /api/system/codex-restart Refresh the catalog, then restart stale Codex app-servers and fully quit and relaunch the Codex desktop app so the model picker reloads. When the proxy itself is running inside the Codex app, the desktop restart is refused rather than handed off. Returns 200 with code: partially_stopped when a target survives

GET /api/settings reports the effective codexAccountPickerEnabled boolean. A PUT containing that strict boolean initializes privacy-safe account selectors when enabling an empty map, preserves existing selector labels when disabling or re-enabling, persists first, and then requests one bounded catalog convergence only when effective picker visibility changed. The successful response includes catalogRefreshPending: false means the catalog commit completed (or no refresh was needed), while true means the setting was saved but POST /api/sync should be used to retry the catalog refresh. Persistence or selector-allocation failure rolls the in-memory settings back and does not run convergence.

The root management dispatcher delegates every /api/codex-auth/* request to the Codex account manager. Its routes are:

Method and path Purpose Notable errors
GET, POST, DELETE /api/codex-auth/accounts List/refresh or delete Codex accounts. POST is retained as a disabled compatibility endpoint; successful DELETE responses include catalogRefreshPending. POST always returns 403 manual_import_disabled; 400 invalid DELETE input
PUT /api/codex-auth/accounts/alias Set or clear an account alias 400 invalid account/alias
PUT /api/codex-auth/accounts/pause Pause or resume one account 400 invalid account/state; 404 missing account
PUT /api/codex-auth/accounts/pause-exhausted Pause accounts whose quota is exhausted Mutation-lock failures become 503
PUT /api/settings with codexQuotaAutoRefresh: { id, window, enabled } Enable or disable 5-hour or weekly automatic window activation for one account 400 invalid id/window/state; 404 missing account; 409 unavailable window
POST /api/codex-auth/accounts/clear-cooldown Clear runtime cooldown for one account or all accounts 400 invalid id
GET, PUT /api/codex-auth/active Read or select the active account 400 invalid or missing account; 409 paused/legacy-row conflict
PUT /api/codex-auth/auto-switch Set the quota threshold for automatic account switching 400 invalid threshold
PUT, PATCH /api/codex-auth/pool-strategy Update Codex account-pool selection strategy 400 invalid strategy/config
PUT /api/codex-auth/failover Set the account failover threshold 400 invalid threshold
GET /api/codex-auth/quota Read cached quota state by account
GET /api/codex-auth/reset-credits Inspect reset-credit eligibility for an account 400 missing account id; upstream status passthrough; 500 lookup failure
POST /api/codex-auth/reset-credits/consume Consume an eligible reset credit. Optional operationId (UUIDv4) makes the redemption idempotent: the same id replays one durable outcome instead of spending a second credit. 400 missing account id or invalid operationId; 409 identity_mismatch when the id belongs to another account; upstream status passthrough; 503 server_busy, capacity, or unavailable; 500 consume failure
POST /api/codex-auth/login Start Codex login or reauthentication 400 invalid request; conflict/busy login states
POST /api/codex-auth/login/code Submit a manual code for a Codex login flow 400 invalid flow/code
POST /api/codex-auth/login/cancel Cancel a Codex login flow
GET /api/codex-auth/login-status Poll a flow or account login state. A completed new-account flow includes catalogRefreshPending: true only when recovery is needed. Unknown flows report expired; no active flow reports idle

For reset-credit consumption, a different operationId supplied while the same physical account has an unfinished operation joins that operation as an alias. Its retry uses the original upstream request ID and records the outcome under that same identity, so later requests with the original ID or a known alias replay the stored result without another consume request. A previously unseen ID supplied after settlement starts a new explicit redemption; clients retrying an existing action should keep its ID.

After a confirmed manual reset, OpenCodex checks fresh usage for that same account and can reconcile its eligible pre-existing shared reset-derived cooldown immediately. Paused accounts, accounts requiring reauthentication and cooldowns already owned by an in-flight probe remain excluded from this recovery; their cooldowns are retained. Usage started before the reset, incomplete or exhausted usage, a changed account, and a newer quota failure do not qualify. Older main-account usage responses cannot replace a newer published observation. If usage needs credential refresh, recovery requires that refresh’s confirmed lineage; an externally replaced credential does not qualify merely because it belongs to the same account. Explicit Retry-After, Reserve cooldowns, pause settings, pins and the selected account are preserved. already_redeemed and durable replay do not prove a new reset and do not gain this recovery behavior.

A failed or busy usage refresh after a confirmed reset or already_redeemed does not turn the completed consumption into an error: the response remains HTTP 200 with its consume code, omitting remaining when no fresh count was obtained. This response confirms the consume outcome, not that the account is now routable. Refresh usage to check availability; do not consume another credit to retry a failed usage refresh.

If a new account config row is saved but credential setup cannot finish, OAuth login-status reports status: "error" with code: "codex_credential_persistence_failed", accountId, needsReauth: true, and optional catalogRefreshPending: true; storage-error details are not exposed. The account row remains saved: reauthenticate or delete it before retrying account creation.

Configuration-writer or credential-refresh lock timeouts under this delegated family return HTTP 503 with code CONFIG_MUTATION_LOCK_UNAVAILABLE. Clients should retry shortly rather than treating that response as a permanent account failure.

Account creation and deletion commit credentials/configuration before catalog convergence. A failed or deferred catalog attempt never rolls back the durable account mutation and never reflects internal provider, account, path, or credential details; clients receive only the completion boolean. Deleting an account retains its selector binding so exact routes fail closed while the account is absent and the same selector is restored if that account id is added again.

For ordinary administration, the Web Dashboard gives the safest guided workflow. For headless hosts and automation, use the corresponding ocx commands: they call this same live API and return a nonzero result when the proxy is unreachable or the operation fails. Direct HTTP is most useful for integrations that need the exact endpoint contracts above.

POST /api/keys/rotate {id} starts a ten-minute overlap and returns the new data secret once. POST /api/keys/rotate/commit {id,rotationId} commits it; DELETE /api/keys/rotate {id,rotationId} aborts it. All require management authentication; data keys cannot call them. POST /api/session/logout requires the current gui-session, matching Origin, and CSRF. An admin token receives 403 and can never mint or exchange into a consent session.