# Quickstart

> End-to-end first connection: `initialize` / `initialized`, `thread/start`, `turn/start`, read `item/*` and `turn/completed`, with verification and one recovery note.

- 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/initialize_processor.rs`
- `src/request_processors/thread_processor.rs`
- `src/request_processors/turn_processor.rs`
- `tests/common/test_app_server.rs`
- `tests/suite/v2/thread_list.rs`

---

---
title: "Quickstart"
description: "End-to-end first connection: `initialize` / `initialized`, `thread/start`, `turn/start`, read `item/*` and `turn/completed`, with verification and one recovery note."
---

`codex app-server` exposes a JSON-RPC 2.0 API over newline-delimited JSON on stdio by default (`--listen stdio://`). A first integration opens one transport connection, completes the per-connection `initialize` handshake, starts a thread, runs one turn, and reads streaming `item/*` notifications until `turn/completed`.

## Prerequisites

| Requirement | Notes |
| --- | --- |
| Codex CLI | Provides `codex app-server` and the `codex-app-server` binary used in integration tests. |
| `CODEX_HOME` | Server reads config and rollouts from this directory; tests set it on the child process. |
| JSON-RPC client | Must write one JSON object per line to stdin and read the same from stdout. |
| Monotonic request ids | Integer ids are typical; match responses and errors by `id`. |

<Note>
Wire messages omit the `"jsonrpc":"2.0"` header on the wire, matching MCP-style framing described in the app-server README.
</Note>

## End-to-end flow

```mermaid
sequenceDiagram
    participant Client
    participant Transport as stdio JSONL transport
    participant MP as MessageProcessor
    participant TP as ThreadRequestProcessor
    participant TurnP as TurnRequestProcessor

    Client->>Transport: initialize (clientInfo)
    Transport->>MP: ClientRequest::Initialize
    MP-->>Client: InitializeResponse
    Client->>Transport: initialized notification
    Client->>Transport: thread/start
    Transport->>TP: thread_start
    TP-->>Client: ThreadStartResponse
    TP-->>Client: thread/started notification
    Client->>Transport: turn/start (threadId, input)
    Transport->>TurnP: turn_start
    TurnP-->>Client: TurnStartResponse (turn inProgress)
    TurnP-->>Client: turn/started
    TurnP-->>Client: item/started → item/* deltas → item/completed
    TurnP-->>Client: turn/completed
```

After `thread/start`, the connection is auto-subscribed to turn and item events for that thread. After `turn/start`, keep reading stdout (or your transport reader) for notifications even while the RPC response has already returned.

## Run the server

Default transport is stdio:

```bash
codex app-server
```

Integration tests spawn `codex-app-server` with `CODEX_HOME` set and stdin/stdout piped. Your client should treat the child process the same way: one JSON-RPC object per line on stdin, one object per line on stdout.

<Info>
Set `RUST_LOG` for log verbosity and `LOG_FORMAT=json` for structured logs on stderr. Server diagnostics do not mix into the JSON-RPC stdout stream.
</Info>

## Step 1: Connect and initialize

<Steps>
<Step title="Send initialize">

<RequestExample>
```json
{
  "method": "initialize",
  "id": 0,
  "params": {
    "clientInfo": {
      "name": "my_integration",
      "title": "My Integration",
      "version": "0.1.0"
    }
  }
}
```
</RequestExample>

<ParamField body="clientInfo.name" type="string" required>
HTTP-header-safe client identifier. Real clients use stable names such as `codex_vscode` for compliance logging. Invalid names are rejected before the connection is marked initialized.
</ParamField>

<ParamField body="clientInfo.version" type="string" required>
Client version string; combined with `name` for the upstream user agent suffix on originating clients.
</ParamField>

<ParamField body="capabilities.experimentalApi" type="boolean">
Opt in to experimental RPCs and fields for this connection.
</ParamField>

<ParamField body="capabilities.optOutNotificationMethods" type="string[]">
Exact notification method names to suppress on this connection (for example `item/agentMessage/delta`). Matching is exact; unknown names are ignored.
</ParamField>

</Step>

<Step title="Read the initialize result">

<ResponseExample>
```json
{
  "id": 0,
  "result": {
    "userAgent": "my_integration/0.1.0 …",
    "codexHome": "/Users/me/.codex",
    "platformFamily": "unix",
    "platformOs": "macos"
  }
}
```
</ResponseExample>

<ResponseField name="userAgent" type="string">
User agent the server presents to upstream services after initialization.
</ResponseField>

<ResponseField name="codexHome" type="string">
Resolved Codex home directory for this server process.
</ResponseField>

<ResponseField name="platformFamily" type="string">
Runtime platform family (`std::env::consts::FAMILY`).
</ResponseField>

<ResponseField name="platformOs" type="string">
Runtime OS name (`std::env::consts::OS`).
</ResponseField>

**Verify:** `id` matches your request; `codexHome` points at the expected config tree; `userAgent` reflects `clientInfo.name` and `version` for originating clients.

</Step>

<Step title="Send initialized">

Acknowledge the handshake with a client notification (no `params` field):

<RequestExample>
```json
{
  "method": "initialized"
}
```
</RequestExample>

The integration harness sends this immediately after a successful `initialize` response. Treat it as part of your client protocol even though the server currently logs inbound client notifications rather than gating RPCs on this message—session readiness is established when `initialize` succeeds.

</Step>
</Steps>

### Initialization errors

| Message | When | Recovery |
| --- | --- | --- |
| `Not initialized` | Any RPC before `initialize` on that connection | Run `initialize` (then `initialized`) before other methods. |
| `Already initialized` | Second `initialize` on the same connection | Open a new transport connection; initialization is once per connection. |

## Step 2: Start a thread

<RequestExample>
```json
{
  "method": "thread/start",
  "id": 10,
  "params": {
    "model": "gpt-5.1-codex",
    "cwd": "/Users/me/project"
  }
}
```
</RequestExample>

<ResponseExample>
```json
{
  "id": 10,
  "result": {
    "thread": {
      "id": "67e55044-10b1-426f-9247-bb680e5fe0c8",
      "preview": "",
      "modelProvider": "openai",
      "createdAt": 1730910000
    }
  }
}
```
</ResponseExample>

You should also receive a server notification:

```json
{ "method": "thread/started", "params": { "thread": { } } }
```

`thread/start` creates the thread, returns it in the RPC result, emits `thread/started` (including current `thread.status`), and auto-subscribes this connection to turn and item notifications for that thread.

**Verify:** Persist `result.thread.id`; confirm `thread/started` arrives unless you opted out via `optOutNotificationMethods`.

## Step 3: Start a turn

<RequestExample>
```json
{
  "method": "turn/start",
  "id": 30,
  "params": {
    "threadId": "67e55044-10b1-426f-9247-bb680e5fe0c8",
    "clientUserMessageId": "client_msg_1",
    "input": [
      { "type": "text", "text": "Run tests" }
    ]
  }
}
```
</RequestExample>

<ResponseExample>
```json
{
  "id": 30,
  "result": {
    "turn": {
      "id": "turn_456",
      "status": "inProgress",
      "items": [],
      "error": null
    }
  }
}
```
</ResponseExample>

`turn/start` returns immediately with a `turn` in `inProgress` and an empty `items` array. Generation continues asynchronously; item data arrives only through notifications.

<ParamField body="threadId" type="string" required>
Target thread from `thread/start` (or `thread/resume` / `thread/fork`).
</ParamField>

<ParamField body="input" type="UserInput[]" required>
Discriminated user inputs: `text`, `image`, `localImage`, `skill`, `mention`, and related variants.
</ParamField>

<ParamField body="clientUserMessageId" type="string">
Optional id echoed on the `userMessage` item as `clientId`.
</ParamField>

## Step 4: Read item and turn notifications

Keep reading the transport after `turn/start`. A minimal successful turn typically includes:

| Order | Method | Purpose |
| --- | --- | --- |
| 1 | `turn/started` | Turn is running; `{ turn }` with `status: "inProgress"`. |
| 2 | `item/started` | New transcript item (for example `userMessage`, `agentMessage`, `commandExecution`). |
| 3 | `item/*` deltas | Streaming updates (`item/agentMessage/delta`, `item/commandExecution/outputDelta`, …). |
| 4 | `item/completed` | Final state for that item id. |
| 5 | `turn/completed` | Terminal turn; `turn.status` is `completed`, `interrupted`, or `failed`. |

Per-item lifecycle is always **`item/started` → zero or more deltas → `item/completed`**. Turn-level bookends are **`turn/started`** and **`turn/completed`**.

Example notification shapes:

```json
{ "method": "turn/started", "params": { "threadId": "…", "turn": { "id": "…", "status": "inProgress", "items": [] } } }
```

```json
{ "method": "item/started", "params": { "threadId": "…", "turnId": "…", "item": { "type": "userMessage", "id": "…" } } }
```

```json
{ "method": "turn/completed", "params": { "threadId": "…", "turn": { "id": "…", "status": "completed", "items": [] } } }
```

<Warning>
`turn/started` and `turn/completed` currently carry an empty `items` array even when item events streamed. Build UI state from `item/*` notifications until that behavior changes.
</Warning>

### Verification checklist

Use this after your first live turn (or when mirroring the integration test pattern):

- [ ] `initialize` returned `userAgent`, `codexHome`, `platformFamily`, and `platformOs`.
- [ ] `thread/start` returned a non-empty `thread.id`.
- [ ] `thread/started` matched that id (unless opted out).
- [ ] `turn/start` returned `turn.status === "inProgress"` and a non-empty `turn.id`.
- [ ] `turn/started` referenced the same `threadId` and `turn.id`.
- [ ] At least one `item/started` / `item/completed` pair appeared for work you care about (for example `userMessage` then `agentMessage`).
- [ ] `turn/completed` arrived with `turn.status === "completed"` (or `interrupted` / `failed` with `turn.error` populated).

Tests in `tests/suite/v2/turn_start.rs` assert `turn/started` then `turn/completed` with matching ids and `TurnStatus::Completed` for mock-model runs. Item-level tests loop on `item/started` until the expected `ThreadItem` variant appears.

## Recovery: `Not initialized`

If any RPC before `initialize` returns:

```json
{
  "id": 2,
  "error": {
    "code": -32600,
    "message": "Not initialized"
  }
}
```

the connection has not completed the handshake. Fix:

1. Send `initialize` with valid `clientInfo`.
2. Wait for the matching result.
3. Send the `initialized` notification.
4. Retry `thread/start`, `turn/start`, or other methods.

Each transport connection maintains its own session; another client on a different socket cannot initialize for you (verified for websocket in `connection_handling_websocket.rs`). After `"Already initialized"`, spawn a new server connection instead of calling `initialize` again.

<Tip>
If requests fail with code `-32001` and message `"Server overloaded; retry later."`, back off and retry—the server uses bounded ingress queues and treats overload as retryable.
</Tip>

## Minimal client loop (stdio)

```text
spawn: codex app-server
write: {"method":"initialize","id":0,"params":{"clientInfo":{"name":"my_client","version":"0.1.0"}}}
read:  initialize result line
write: {"method":"initialized"}
write: {"method":"thread/start","id":10,"params":{"cwd":"/path/to/project"}}
read:  thread/start result line
read:  thread/started notification line(s)
write: {"method":"turn/start","id":30,"params":{"threadId":"<id>","input":[{"type":"text","text":"Hello"}]}}
read:  turn/start result line
loop:  read notifications until turn/completed for turn id
```

Match each response and error to the outstanding request `id`. Interleave handling of server-initiated requests (approvals, attestation) if your config triggers them—those can arrive mid-turn.

## Related pages

<CardGroup>
<Card title="Installation" href="/installation">
Launch `codex-app-server`, default listen URLs, and stdout/stderr success signals.
</Card>
<Card title="Protocol and transport" href="/protocol-and-transport">
JSON-RPC framing, transports, health probes, and overload code `-32001`.
</Card>
<Card title="Connection lifecycle" href="/connection-lifecycle">
Per-connection handshake, `clientInfo`, notification opt-out, and subscribe semantics.
</Card>
<Card title="Stream turns and events" href="/stream-turns-and-events">
Full turn/item notification catalog and delta types.
</Card>
<Card title="Build a JSON-RPC client" href="/build-jsonrpc-client">
Framing, request ids, `initialized` ordering, and compliance-oriented client names.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
Deeper diagnosis for init failures, overload, and turn errors.
</Card>
</CardGroup>
