โ† Back to Blogcodex app-server: How to Embed Codex Into Your Own Application

codex app-server: How to Embed Codex Into Your Own Application

A practical guide to codex app-server, the bidirectional JSON-RPC interface that the Codex extension for VS Code and other clients use to control the agent. We'll cover launching the server and its transports, initialization, working with threads and turns, action approvals, and secure remote access.

๐Ÿ”„ Currency check: verified as of August 24, 2026 against official OpenAI documentation and the app-server README in the openai/codex repository. Part of the API is experimental and changes between versions: before adopting it in production, generate the schema from your installed CLI version (commands are in the protocol section below).

๐Ÿ“Œ In short: use app-server when you're embedding Codex into your own product and need conversation history, streaming events, and action approvals. For automation and CI, use the Codex SDK instead. For one-off, non-interactive terminal runs, use codex exec.

What is codex app-server

codex app-server is a Codex CLI subcommand that spins up a server implementing a bidirectional JSON-RPC 2.0 protocol (a remote procedure call format over JSON). Through it, a client gets access to the full Codex experience: authentication, conversation history, streaming agent events, sandboxed command execution, and action approvals.

This is the exact protocol used by the Codex extension for VS Code, which makes app-server suitable for building your own IDE integrations, web applications, and desktop clients. The implementation is open source: see the codex-rs/app-server directory in the openai/codex repository. The client language doesn't matter โ€” you just need to read and write one JSON message per line. TypeScript, Python, Go, Rust, and other languages all work fine.

Which tool to choose: SDK, exec, MCP, or app-server

ToolUse caseWhy choose it
Codex SDKTask automation, CI, scriptsSimpler and more stable, no manual protocol handling
codex execOne-off non-interactive terminal runNo server or persistent connection needed
Codex as MCP serverExpose Codex's tools to another agentStandard MCP protocol
codex app-serverCustom client with history, streaming, and approvalsFull access to the Codex harness โ€” the same protocol VS Code's extension uses

Launching and transports

npm install -g @openai/codex

# stdio (default): one line = one JSON message
codex app-server

# WebSocket on a loopback interface
codex app-server --listen ws://127.0.0.1:4500

# Default Unix socket
codex app-server --listen unix://
TransportFlagFormat and status
stdio--listen stdio://JSONL over stdin/stdout; default mode for child processes
WebSocket--listen ws://IP:PORTOne message per text frame; experimental, not supported for production
Unix socket--listen unix:// or unix://PATHWebSocket connection over a socket with an HTTP Upgrade
off--listen offNo local transport is opened

The WebSocket listener also answers HTTP health checks: GET /readyz returns 200 OK once the server is accepting connections; GET /healthz returns 200 OK for requests without an Origin header, while requests carrying an Origin header are rejected with 403 Forbidden.

The protocol and message schema

Like MCP, app-server runs on top of JSON-RPC 2.0, but the "jsonrpc": "2.0" header is omitted on the wire.

A request contains method, params, and id:

{ "method": "thread/start", "id": 10, "params": { "model": "gpt-5.4" } }

A response echoes the id and contains result or error:

{ "id": 10, "result": { "thread": { "id": "thr_123" } } }

A notification has no id:

{ "method": "turn/started", "params": { "turn": { "id": "turn_456" } } }

You don't need to hand-write the message schema: the CLI generates TypeScript types or JSON Schema straight from your installed version, and the generated artifacts are guaranteed to match it.

codex app-server generate-ts --out ./schemas
codex app-server generate-json-schema --out ./schemas

๐Ÿ’ก Tip: regenerate the schema after every Codex CLI update. You can check the list of available models and their parameters by calling model/list instead of hardcoding model identifiers in your client.

Lifecycle: initialization, threads, turns

The protocol is built on three primitives:

  • thread โ€” a conversation between a user and the agent, made up of turns
  • turn โ€” a single user request and the agent's work on it
  • item โ€” a unit of input or output: a message, a command run, a file edit, a tool call

Connection lifecycle:

  1. The client sends initialize with clientInfo (client name, title, version) and waits for a response.
  2. The client sends an initialized notification. Before this handshake completes, the server responds to any request with a Not initialized error; a second initialize on the same connection returns Already initialized.
  3. The client opens a conversation: thread/start for a new one, thread/resume to continue from a saved id, or thread/fork to branch the history.
  4. The client starts a turn: turn/start with a threadId and input items (text, an image URL, or a local file).
  5. During the turn, the client reads a stream of notifications: item/started, text deltas, item/completed.
  6. You can add a message to an active turn via turn/steer, or stop it via turn/interrupt.
  7. The turn ends with a turn/completed notification carrying a status of completed, interrupted, or failed.

In initialize.params.capabilities, a client can opt into experimental methods (experimentalApi: true) and disable unneeded notifications by exact method name (optOutNotificationMethods).

โš–๏ธ Note: without experimentalApi, the server rejects experimental methods and fields with an error like <descriptor> requires experimentalApi capability. The opt-in is fixed for the lifetime of the connection.

Method map

GroupKey methods
Threadsthread/start, thread/resume, thread/fork, thread/read, thread/list, thread/archive, thread/unarchive, thread/delete, thread/compact/start, thread/goal/set
Turnsturn/start, turn/steer, turn/interrupt, review/start
Commands and processescommand/exec with write/resize/terminate; process/* โ€” experimental, runs outside the sandbox
Models and flagsmodel/list, experimentalFeature/list, permissionProfile/list
Configurationconfig/read, config/value/write, config/batchWrite, configRequirements/read
Filesfs/readFile, fs/writeFile, fs/readDirectory, fs/watch, and other fs/* methods
Skills and pluginsskills/list, skills/config/write, marketplace/*; plugin/list, plugin/install and related methods are still in development
Apps (connectors)app/list, invoked via a $demo-app marker in input text and a mention item with path app://demo-app
Accountaccount/read, account/login/start (API key, ChatGPT browser flow, device code), account/logout, account/rateLimits/read, account/usage/read

Events and errors

After starting or resuming a thread, the client continuously reads transport notifications. Key ones include:

  • thread/started, thread/archived, thread/closed, thread/status/changed โ€” conversation lifecycle
  • turn/started, turn/completed, turn/diff/updated, turn/plan/updated โ€” turn progress
  • item/started and item/completed โ€” start and finish of each item; read the final item state from item/completed
  • item/agentMessage/delta, item/reasoning/summaryTextDelta, item/commandExecution/outputDelta โ€” streaming deltas of text, reasoning, and command output
  • thread/tokenUsage/updated โ€” token consumption for the active thread

When a turn fails, the server sends an error with a codexErrorInfo field and ends the turn with a failed status. Typical values include ContextWindowExceeded, UsageLimitExceeded, HttpConnectionFailed, ResponseStreamDisconnected, and SandboxError. If an HTTP status from an upstream service is available, it arrives in httpStatusCode.

Overload is a special case. When the incoming queue fills up, the server rejects new requests with code -32001 and the message Server overloaded; retry later. Clients should retry with exponential backoff and jitter.

Approvals and sandboxing

Depending on user settings, command execution and file edits may require approval. The server sends the client a request, and the client responds with a decision.

Message sequence for a command:

  1. item/started with a commandExecution item.
  2. A request: item/commandExecution/requestApproval with itemId, threadId, turnId, and command details.
  3. The client's response: accept, acceptForSession, decline, or cancel; for commands there's also acceptWithExecpolicyAmendment โ€” accept the command and simultaneously add a rule to the execpolicy.
  4. serverRequest/resolved confirms the request has been closed.
  5. item/completed with a final status of completed, failed, or declined.

File edits follow the same pattern: item/fileChange/requestApproval with the same set of decisions. If a request includes networkApprovalContext, it's a request for network access to a specific host and protocol โ€” show a network-access dialog rather than a shell-command preview.

The isolation policy is set via sandboxPolicy when starting a thread or turn: readOnly, workspaceWrite with explicit writableRoots, externalSandbox (if isolation is already handled by your own environment), or dangerFullAccess. The approval policy is set via approvalPolicy.

๐Ÿ”ด Critical: the thread/shellCommand method runs outside the sandbox with full access and does not inherit the thread's policy. Only expose it in your client for commands explicitly initiated by the user. The same applies to the experimental process/* methods.

The built-in request_permissions tool arrives as an item/permissions/requestApproval request. In your response, grant only the requested subset of permissions and explicitly choose the scope: scope: "turn" for a single turn or "session" for the whole session.

Remote access and authorization

You can run app-server on one machine and connect Codex's terminal interface from another:

# on the host
codex app-server --listen ws://127.0.0.1:4500

# on the client
codex --remote ws://127.0.0.1:4500

The --remote option accepts ws://, wss://, and unix:// endpoints. For an unencrypted channel, keep the listener on loopback or tunnel the port over SSH. For a genuinely remote connection, enable authorization and TLS, and pass the token via an environment variable:

export CODEX_REMOTE_TOKEN="$(cat "$HOME/.codex/app-server-token")"
codex --remote wss://remote-host:4500 \
  --remote-auth-token-env CODEX_REMOTE_TOKEN

โš ๏ธ Warning: during the gradual rollout period, non-local WebSocket listeners accept connections without authorization by default. Before exposing a port externally, configure --ws-auth: either a capability token via --ws-token-file or --ws-token-sha256, or a signed bearer token via --ws-shared-secret-file. Never pass a raw token as a command-line argument.

Useful scenarios

"Ask Codex" right inside your own product

The task: your team has an internal tool โ€” an admin panel, editor, or internal portal โ€” and you want employees to be able to ask Codex a question without leaving it, complete with conversation history, live-typing responses, and allow/deny buttons for the agent's actions.

Prerequisites: Codex CLI installed on the machine running the service, plus a small amount of code in any language โ€” the protocol only requires reading and writing JSON line by line.

What to do: the service launches codex app-server as a child process, completes the initialize/initialized handshake, opens a thread via thread/start, and sends the user's question via turn/start. The agent's response arrives as item/agentMessage/delta chunks and is displayed immediately in the UI. When the agent asks for permission to run a command or make an edit, the interface receives a standard request and responds with the user's decision.

A minimal Node.js client looks like this:

import { spawn } from "node:child_process";
import readline from "node:readline";

const proc = spawn("codex", ["app-server"], {
  stdio: ["pipe", "pipe", "inherit"],
});

const rl = readline.createInterface({ input: proc.stdout });

const send = (message) => {
  proc.stdin.write(`${JSON.stringify(message)}\n`);
};

let threadId = null;

rl.on("line", (line) => {
  const msg = JSON.parse(line);
  if (msg.id === 1 && msg.result?.thread?.id && !threadId) {
    threadId = msg.result.thread.id;
    send({
      method: "turn/start",
      id: 2,
      params: {
        threadId,
        input: [{ type: "text", text: "Summarize this repo." }],
      },
    });
  }
});

send({
  method: "initialize",
  id: 0,
  params: {
    clientInfo: { name: "my_product", title: "My Product", version: "0.1.0" },
  },
});
send({ method: "initialized", params: {} });
send({ method: "thread/start", id: 1, params: { model: "gpt-5.4" } });

Observed result: the user watches the response type out as it's generated; the turn finishes with a turn/completed notification and a completed status, and the assembled text can be read from item/completed.

Limitation: only one initialize per connection is allowed; experimental methods require capabilities.experimentalApi at initialization time.

Codex stays home, you work from a laptop

The task: your projects and configured Codex setup live on a desktop machine โ€” say, a home Mac mini โ€” but you've traveled with your laptop and want to keep working in the familiar terminal interface.

What to do: on the home machine, run codex app-server --listen ws://127.0.0.1:4500 and tunnel the port over SSH. On the laptop, connect with codex --remote ws://127.0.0.1:4500. Store the token in a file and pass it via an environment variable, as shown in the remote access section above.

Observed result: the terminal on your laptop looks like a normal Codex session, but commands execute on the home machine; curl -sf http://127.0.0.1:4500/readyz returns 200 OK.

Limitation: the WebSocket transport is experimental; don't expose it externally without --ws-auth and TLS.

Updating Codex CLI without surprises

The task: you've updated Codex CLI and want to know in advance whether the new version has broken your integration.

What to do: after every update, run codex app-server generate-ts --out ./schemas (or generate-json-schema for other languages) and rebuild your client.

Observed result: any mismatches between your client and the new protocol version surface immediately at build time, before deployment.

Limitation: the schema only matches the CLI version it was generated from, so it makes sense to keep the generated files alongside your client code.

Verifying the result

A minimal working scenario to validate your integration:

  1. Run codex app-server --listen ws://127.0.0.1:4500.
  2. Run curl -sf http://127.0.0.1:4500/readyz: a 200 OK response confirms the listener is accepting connections.
  3. Send initialize, wait for a response containing the server's user-agent, then send initialized.
  4. Run thread/start, followed by turn/start with a simple text request.

Success looks like a stream of item/* notifications followed by a final turn/completed with a completed status. A Not initialized error means the handshake order was violated.

Limitations and when it doesn't fit

The app-server command and its WebSocket transport are officially experimental and not supported for production workloads. For CI and direct automation, the documentation recommends the Codex SDK โ€” app-server's protocol is broader and designed specifically for rich client integrations.

The plugin/list, plugin/read, plugin/install, and plugin/uninstall methods are marked as still under development; avoid calling them from production clients.

thread/rollback and the item/fileChange/outputDelta notification are deprecated; use fileChange items and turn/diff/updated for edits instead.

Under overload, expect -32001 errors and retry with exponential backoff and jitter.

This guide is based on official OpenAI documentation and the app-server README, rather than exhaustively testing every method locally. Check experimental flag behavior against your own CLI version.

Official sources

  • Codex App Server documentation
  • README and source: openai/codex repository, codex-rs/app-server directory
  • Codex CLI command reference
  • OpenAI's article on App Server architecture

app-server opens the door to building your own interfaces and workflows around Codex. If you're embedding an agent into a product or a team process, it's worth thinking through the integration architecture before the protocol gets locked into your codebase.

For AI Agents

Read with AI

Short prompt for a summary, takeaways, and applying this to your task.

ChatGPTClaude

Want to discuss your own task?

Tell us about the workflow you want to improve. We will help you identify the practical next step.

Request a free consultationExplore our services