# Account, auth, and config

> Account login flows (`apiKey`, `chatgpt`, device code), config read/write/batch RPC, requirements.toml constraints, and hot-reload behavior after `config/batchWrite`.

- Repository: openai/codex
- GitHub: https://github.com/openai/codex
- Human docs: https://grok-wiki.com/public/docs/openai-codex-c82680b15ec1
- Complete Markdown: https://grok-wiki.com/public/docs/openai-codex-c82680b15ec1/llms-full.txt

## Source Files

- `README.md`
- `src/request_processors/account_processor.rs`
- `src/request_processors/config_processor.rs`
- `src/config_manager.rs`
- `src/config_manager_service.rs`
- `tests/suite/v2/config_rpc.rs`
- `tests/suite/auth.rs`

---

---
title: "Account, auth, and config"
description: "Account login flows (`apiKey`, `chatgpt`, device code), config read/write/batch RPC, requirements.toml constraints, and hot-reload behavior after `config/batchWrite`."
---

`codex app-server` exposes account lifecycle, configuration persistence, and policy constraints as JSON-RPC v2 methods handled by `AccountRequestProcessor` and `ConfigRequestProcessor`, backed by `ConfigManager` / `ConfigManagerService` for layered config resolution and user `config.toml` writes.

```text
  Client JSON-RPC
        |
        v
  MessageProcessor
   /            \
  v              v
AccountRequest   ConfigRequest
Processor        Processor
  |                |
  v                v
AuthManager      ConfigManager ----> ConfigManagerService
  |                |                      |
  v                +--> load_config_layers / apply_edits
login_with_*         |
refresh tokens       +--> $CODEX_HOME/config.toml (user layer only)
```

## Account and authentication RPCs

| Method | Role |
| --- | --- |
| `account/read` | Current account (`apiKey`, `chatgpt`, `amazonBedrock`, or `null`); optional `refreshToken` |
| `account/login/start` | Start login (`apiKey`, `chatgpt`, `chatgptDeviceCode`, experimental `chatgptAuthTokens`) |
| `account/login/cancel` | Cancel in-flight browser or device-code login by `loginId` |
| `account/logout` | Revoke and clear credentials; emits `account/updated` |
| `account/rateLimits/read` | ChatGPT rate-limit snapshot (requires ChatGPT / Codex backend auth) |
| `account/sendAddCreditsNudgeEmail` | Notify workspace owner (`creditType`: `credits` or `usage_limit`) |
| `getAuthStatus` | Legacy v1 probe: `authMethod`, optional `authToken`, `requiresOpenaiAuth` |
| `account/chatgptAuthTokens/refresh` | **Server request** (not client RPC) when external auth needs new tokens |

Notifications:

| Notification | When |
| --- | --- |
| `account/login/completed` | Any login attempt finishes (`loginId`, `success`, `error`) |
| `account/updated` | Auth mode or ChatGPT `planType` changes (`authMode`: `apikey`, `chatgpt`, `chatgptAuthTokens`, `agentIdentity`, or `null`) |
| `account/rateLimits/updated` | Sparse rolling rate-limit deltas |

<Info>
`authMode` values use lowercase wire names (`apikey`, `chatgpt`, …). `account/read` returns structured `account` objects with camelCase variant tags (`apiKey`, `chatgpt`, `amazonBedrock`).
</Info>

### Login flows

:::endpoint POST account/login/start Begin authentication

**`type: "apiKey"`** — Synchronous. Persists the key under `codex_home`, reloads `AuthManager`, responds with `{ "type": "apiKey" }`, then emits `account/login/completed` and `account/updated`. Cancels any active browser/device login first.

**`type: "chatgpt"`** — Browser OAuth. Returns `{ "type": "chatgpt", "loginId", "authUrl" }` immediately; app-server runs a local callback server (`open_browser: false`). A background task waits up to **10 minutes**, then emits completion notifications. On success: reload auth, refresh cloud requirements loader, sync residency requirement, optionally refresh remote plugin cache.

**`type: "chatgptDeviceCode"`** — Device code flow. Returns `{ "loginId", "verificationUrl", "userCode" }`; polls completion in the background. Cancel via `account/login/cancel` or replacement login.

**`type: "chatgptAuthTokens"`** (experimental) — External/host-managed ChatGPT tokens. Client supplies `accessToken`, `chatgptAccountId`, optional `chatgptPlanType`. Tokens stay in memory; refresh is the client’s responsibility via another `chatgptAuthTokens` login or answering `account/chatgptAuthTokens/refresh` server requests.

Optional param on browser login: `codexStreamlinedLogin` (boolean, default omitted/false).

:::

<Steps>
<Step title="API key login">

<RequestExample>

```json
{ "method": "account/login/start", "id": 1, "params": { "type": "apiKey", "apiKey": "sk-…" } }
```

</RequestExample>

<ResponseExample>

```json
{ "id": 1, "result": { "type": "apiKey" } }
```

</ResponseExample>

Expect `account/login/completed` then `account/updated` with `"authMode": "apikey"`.

</Step>

<Step title="ChatGPT browser login">

<RequestExample>

```json
{ "method": "account/login/start", "id": 2, "params": { "type": "chatgpt" } }
```

</RequestExample>

<ResponseExample>

```json
{ "id": 2, "result": { "type": "chatgpt", "loginId": "<uuid>", "authUrl": "https://…" } }
```

</ResponseExample>

Open `authUrl` in your UI; wait for `account/login/completed` with matching `loginId`.

</Step>

<Step title="Device code login">

<RequestExample>

```json
{ "method": "account/login/start", "id": 3, "params": { "type": "chatgptDeviceCode" } }
```

</RequestExample>

<ResponseExample>

```json
{
  "id": 3,
  "result": {
    "type": "chatgptDeviceCode",
    "loginId": "<uuid>",
    "verificationUrl": "https://auth.openai.com/codex/device",
    "userCode": "ABCD-1234"
  }
}
```

</ResponseExample>

Display `verificationUrl` and `userCode`; handle completion notifications like the browser flow.

</Step>
</Steps>

### `getAuthStatus` (v1)

Use when you need a lightweight auth probe without full account metadata.

<ParamField body="includeToken" type="boolean | null">
When `true`, include bearer token in `authToken` when available.
</ParamField>

<ParamField body="refreshToken" type="boolean | null">
When `true`, proactively refresh managed ChatGPT tokens before responding. Ignored for external `chatgptAuthTokens` auth.
</ParamField>

<ResponseField name="requiresOpenaiAuth" type="boolean | null">
`false` when the active model provider sets `requires_openai_auth = false` (local/OSS providers). Otherwise `true` and credentials may be required.
</ResponseField>

Permanent refresh failures still return `authMethod` but omit `authToken` even when `includeToken` is true.

### Policy and external-auth constraints

| Constraint | Behavior |
| --- | --- |
| `forced_login_method = "chatgpt"` in config | Rejects `apiKey` login |
| `forced_login_method = "api"` | Rejects ChatGPT login flows |
| `forced_chatgpt_workspace_id` | Restricts `chatgptAuthTokens` to listed workspace IDs |
| Active external ChatGPT auth | Blocks managed `apiKey` / `chatgpt` / `chatgptDeviceCode` until logout or `chatgptAuthTokens` update |

<Warning>
While external auth is active, managed login returns: `External auth is active. Use account/login/start (chatgptAuthTokens) to update it or account/logout to clear it.`
</Warning>

On `401` during a turn with external auth, app-server sends `account/chatgptAuthTokens/refresh` to the client (10s timeout). The client must respond with fresh tokens; mismatched workspace or invalid tokens fail the turn.

## Config RPCs

| Method | Writes disk | Hot-reloads threads |
| --- | --- | --- |
| `config/read` | No | No |
| `config/value/write` | User `config.toml` | No (clears plugin/skill caches) |
| `config/batchWrite` | User `config.toml` (atomic) | Optional via `reloadUserConfig` |
| `configRequirements/read` | No | No |
| `experimentalFeature/enablement/set` | In-memory runtime flags | Yes (always reloads) |

### Wire naming

- **Request/response envelopes** use camelCase (`includeLayers`, `keyPath`, `mergeStrategy`, `reloadUserConfig`).
- **`config` object in `config/read`** uses **snake_case** field names mirroring `config.toml` (`sandbox_mode`, `model_provider`, `forced_login_method`, …).
- **`keyPath` segments** use the same snake_case paths as on disk (`sandbox_mode`, `hooks.state`, `desktop.someKey`). Quoted segments support special characters (`profiles` tables are rejected for writes).

### `config/read`

<ParamField body="includeLayers" type="boolean">
Default `false`. When `true`, returns per-layer configs plus `origins` map (which layer won for each key).
</ParamField>

<ParamField body="cwd" type="string | null">
Optional absolute working directory to include project `.codex/` layers between `cwd` and the repo root.
</ParamField>

<ResponseField name="config" type="object">
Effective merged configuration after layer resolution.
</ResponseField>

<ResponseField name="origins" type="object">
Map from dotted key paths to winning `ConfigLayerSource` (user, project, system, MDM, enterprise-managed, session flags, …).
</ResponseField>

Runtime feature flags under `config.additional.features` are injected for supported keys (`apps`, `memories`, `mentions_v2`, `plugins`, `remote_control`, `remote_plugin`, `tool_suggest`, `tool_call_mcp_elicitation`) reflecting effective enablement.

### `config/value/write` and `config/batchWrite`

Both routes persist **only** the user layer at `$CODEX_HOME/config.toml` (or an explicit `filePath` that normalizes to the same path). Managed, project, MDM, and enterprise layers are read-only through this API.

<ParamField body="keyPath" type="string" required>
Dotted path; `null` JSON value clears the key.
</ParamField>

<ParamField body="mergeStrategy" type="replace | upsert" required>
`replace` sets the value. `upsert` deep-merges when both existing and new values are tables.
</ParamField>

<ParamField body="expectedVersion" type="string | null">
Optimistic concurrency token from the last read/write `version`. Mismatch → `configVersionConflict`.
</ParamField>

<ParamField body="reloadUserConfig" type="boolean">
**`config/batchWrite` only.** When `true`, reload effective config into every loaded thread after a successful write.
</ParamField>

<ResponseField name="status" type="ok | okOverridden">
`okOverridden` when a higher-precedence layer still wins for at least one edited key; check `overriddenMetadata` for the effective value and overriding layer message.
</ResponseField>

<ResponseField name="version" type="string">
Pass as `expectedVersion` on the next write.
</ResponseField>

Write errors attach `data.config_write_error_code`:

| Code | Typical cause |
| --- | --- |
| `configLayerReadonly` | Target path is not user config |
| `configVersionConflict` | Stale `expectedVersion` |
| `configValidationError` | Invalid TOML shape, feature requirements, legacy `profile`/`profiles` writes |
| `userLayerNotFound` | Internal layer resolution failure after write |

<RequestExample>

```json
{
  "method": "config/batchWrite",
  "id": 10,
  "params": {
    "edits": [
      {
        "keyPath": "sandbox_mode",
        "value": "workspace-write",
        "mergeStrategy": "replace"
      },
      {
        "keyPath": "hooks.state",
        "value": { "/path/to/config.toml:pre_tool_use:0:0": { "enabled": false } },
        "mergeStrategy": "upsert"
      }
    ],
    "reloadUserConfig": true
  }
}
```

</RequestExample>

Every successful config mutation clears the plugins and skills manager caches. MCP servers are **not** restarted automatically—use `config/mcpServer/reload` after MCP-related edits if threads are already loaded.

## `configRequirements/read`

Returns merged constraints from `requirements.toml`, MDM, and cloud loaders, or `requirements: null` when nothing is configured.

<ResponseField name="requirements" type="object | null">
Policy envelope (camelCase fields), including:
</ResponseField>

- `allowedApprovalPolicies`, `allowedSandboxModes`, `allowedWebSearchModes`, `allowedPermissions`
- `allowManagedHooksOnly`, `allowAppshots`, `computerUse`
- `featureRequirements` — pinned feature booleans
- `hooks` — managed lifecycle hook definitions
- `enforceResidency` — e.g. `us`
- `network` — domain/socket permissions, `managedAllowedDomainsOnly`, proxy flags

Writes validate against `feature_requirements` in the effective layer stack; violating enablement returns `configValidationError`.

Cloud requirements reload when ChatGPT login succeeds (`replace_cloud_requirements_loader` + `sync_default_client_residency_requirement`).

## Hot reload after `config/batchWrite`

When `reloadUserConfig` is `true`:

```mermaid
sequenceDiagram
  participant Client
  participant ConfigProcessor
  participant ConfigManager
  participant ThreadManager

  Client->>ConfigProcessor: config/batchWrite (reloadUserConfig: true)
  ConfigProcessor->>ConfigManager: batch_write → persist config.toml
  ConfigProcessor->>ConfigProcessor: handle_config_mutation (clear caches)
  ConfigProcessor->>ConfigManager: load_latest_config
  loop each loaded thread_id
    ConfigProcessor->>ThreadManager: get_thread
    ThreadManager-->>ConfigProcessor: Thread
    ConfigProcessor->>Thread: refresh_runtime_config(next_config)
  end
  ConfigProcessor-->>Client: ConfigWriteResponse
```

<Note>
`config/value/write` and `config/batchWrite` with `reloadUserConfig: false` still invalidate plugin/skill caches but **do not** push new config into active threads. Loaded sessions keep prior runtime settings until the next `reloadUserConfig: true` batch write, a new thread start/resume, or `experimentalFeature/enablement/set` (which always reloads).
</Note>

Integration tests confirm hook trust and `hooks.state` changes affect an in-flight session only when `reloadUserConfig: true`.

## Operational notes

- Debug builds honor `CODEX_APP_SERVER_LOGIN_ISSUER` for ChatGPT login issuer override.
- ChatGPT browser login timeout: **10 minutes**; device-code cancel surfaces `"Login was not completed"`.
- After login/logout, app-server may refresh remote installed plugin caches and queue MCP refresh when threads are loaded.
- For auth worked examples and rate-limit field semantics, see the app-server README auth section; for exhaustive config key reference, see the dedicated config RPC page.

## Related pages

<CardGroup>
<Card title="Config RPC reference" href="/config-rpc">
Snake_case config shapes, MCP reload, external-agent import, and write error catalog.
</Card>
<Card title="Connection lifecycle" href="/connection-lifecycle">
Initialize handshake before account or config RPCs.
</Card>
<Card title="Skills, plugins, and MCP" href="/skills-plugins-and-mcp">
Cache invalidation and `config/mcpServer/reload` after config edits.
</Card>
<Card title="Experimental API" href="/experimental-api">
`chatgptAuthTokens` login and gated `configRequirements` fields.
</Card>
</CardGroup>
