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.
Authentication model
Section titled “Authentication model”The Management API has its own admin credential, independent of data-plane API keys. At startup, opencodex resolves it in this order:
OPENCODEX_ADMIN_AUTH_TOKEN, when set.- 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>Loopback dashboard sessions
Section titled “Loopback dashboard sessions”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.
Common errors
Section titled “Common errors”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 |
Endpoint matrix
Section titled “Endpoint matrix”Agent and client settings
Section titled “Agent and client settings”| 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.
Client integration rollback journal
Section titled “Client integration rollback journal”| 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.
Aside profile controls
Section titled “Aside profile controls”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.
Remote Workspace
Section titled “Remote Workspace”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.
Combos
Section titled “Combos”| 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.
Configuration, startup, sync, and updates
Section titled “Configuration, startup, sync, and updates”| 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 |
Logs, usage, and storage
Section titled “Logs, usage, and storage”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.
ocx models price ollama/custom-model --jsonocx models set-price ollama/custom-model --input 0.50 --output 1.50ocx models set-price ollama/custom-model --input 0 --output 0ocx models set-price ollama/custom-model --autoOmitted 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.
Models and catalog
Section titled “Models and catalog”| 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.
Providers
Section titled “Providers”| 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.
Sidebar and consent-bound actions
Section titled “Sidebar and consent-bound actions”| 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 | — |
System lifecycle
Section titled “System lifecycle”| 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 |
Codex authentication delegation
Section titled “Codex authentication delegation”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.
Choosing a client
Section titled “Choosing a client”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.
Remote sessions and data-key rotation
Section titled “Remote sessions and data-key rotation”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.

