Files
codex/sdk/python/docs/api-reference.md
Ahmed Ibrahim 45134c0463 Generate Python SDK types from repository app-server schemas (#44032)
## Why

Keep Python protocol models aligned with the checked-in app-server schemas and preserve reviewed generated artifacts when staging SDK releases.

## What changed

- Generate SDK types from the schema directory configured in `pyproject.toml`, with a `--schema-dir` override, instead of invoking the pinned runtime binary.
- Refresh Python artifacts through `just write-app-server-schema` for standard repository exports. Skip SDK updates for scratch and experimental exports.
- Regenerate protocol models and notification dispatch, deriving the known payload union from the registry so `Notification.payload` covers every registered event.
- Explicitly allowlist convenience API parameters so new protocol fields do not silently expand method signatures. Preserve existing approval path wrappers.
- Stage SDK releases using checked-in generated files without regenerating them.

## Testing

Add coverage for schema selection, refresh gating and failure handling, release artifact preservation, notification payload typing, and approval path compatibility. Update the generation drift test to use repository schemas.

GitOrigin-RevId: fab350b07cf170258b91fbafa8da384aeb2e3bd7
2026-09-09 03:27:31 +00:00

11 KiB

OpenAI Codex Python SDK - API Reference

Public surface of openai_codex for Codex workflows.

Turn streams are routed by turn ID so one client can consume multiple active turns concurrently. Thread starts default to ApprovalMode.auto_review; turn starts accept an optional approval_mode override.

Package Entry

from openai_codex import (
    Codex,
    AsyncCodex,
    CodexConfig,
    ApprovalMode,
    Sandbox,
    ChatgptLoginHandle,
    DeviceCodeLoginHandle,
    AsyncChatgptLoginHandle,
    AsyncDeviceCodeLoginHandle,
    Thread,
    AsyncThread,
    TurnHandle,
    AsyncTurnHandle,
    TurnResult,
    Input,
    InputItem,
    RunInput,
    TextInput,
    ImageInput,
    LocalImageInput,
    SkillInput,
    MentionInput,
)
from openai_codex.types import (
    Account,
    AccountLoginCompletedNotification,
    CancelLoginAccountResponse,
    CancelLoginAccountStatus,
    GetAccountResponse,
    InitializeResponse,
    ThreadItem,
    ThreadTokenUsage,
    TurnError,
    TurnStatus,
)
  • Version: openai_codex.__version__
  • Requires Python >= 3.10
  • Public Codex protocol value and event types live in openai_codex.types

Codex (sync)

Codex(config: CodexConfig | None = None)

Properties/methods:

  • metadata -> InitializeResponse
  • close() -> None
  • login_api_key(api_key: str) -> None
  • login_chatgpt() -> ChatgptLoginHandle
  • login_chatgpt_device_code() -> DeviceCodeLoginHandle
  • account(*, refresh_token: bool = False) -> GetAccountResponse
  • logout() -> None
  • thread_start(*, approval_mode=ApprovalMode.auto_review, base_instructions=None, config=None, cwd=None, developer_instructions=None, ephemeral=None, model=None, model_provider=None, personality=None, sandbox: Sandbox | None = None) -> Thread
  • thread_list(*, archived=None, cursor=None, cwd=None, limit=None, model_providers=None, sort_key=None, source_kinds=None) -> ThreadListResponse
  • thread_resume(thread_id: str, *, approval_mode=ApprovalMode.auto_review, base_instructions=None, config=None, cwd=None, developer_instructions=None, model=None, model_provider=None, personality=None, sandbox: Sandbox | None = None) -> Thread
  • thread_fork(thread_id: str, *, approval_mode=ApprovalMode.auto_review, base_instructions=None, config=None, cwd=None, developer_instructions=None, model=None, model_provider=None, sandbox: Sandbox | None = None) -> Thread
  • thread_archive(thread_id: str) -> ThreadArchiveResponse
  • thread_unarchive(thread_id: str) -> Thread
  • models(*, include_hidden: bool = False) -> ModelListResponse

Context manager:

with Codex() as codex:
    ...

AsyncCodex (async parity)

AsyncCodex(config: CodexConfig | None = None)

Preferred usage:

async with AsyncCodex() as codex:
    ...

AsyncCodex initializes lazily. Context entry is the standard path because it ensures startup and shutdown are paired explicitly.

Properties/methods:

  • metadata -> InitializeResponse
  • close() -> Awaitable[None]
  • login_api_key(api_key: str) -> Awaitable[None]
  • login_chatgpt() -> Awaitable[AsyncChatgptLoginHandle]
  • login_chatgpt_device_code() -> Awaitable[AsyncDeviceCodeLoginHandle]
  • account(*, refresh_token: bool = False) -> Awaitable[GetAccountResponse]
  • logout() -> Awaitable[None]
  • thread_start(*, approval_mode=ApprovalMode.auto_review, base_instructions=None, config=None, cwd=None, developer_instructions=None, ephemeral=None, model=None, model_provider=None, personality=None, sandbox: Sandbox | None = None) -> Awaitable[AsyncThread]
  • thread_list(*, archived=None, cursor=None, cwd=None, limit=None, model_providers=None, sort_key=None, source_kinds=None) -> Awaitable[ThreadListResponse]
  • thread_resume(thread_id: str, *, approval_mode=ApprovalMode.auto_review, base_instructions=None, config=None, cwd=None, developer_instructions=None, model=None, model_provider=None, personality=None, sandbox: Sandbox | None = None) -> Awaitable[AsyncThread]
  • thread_fork(thread_id: str, *, approval_mode=ApprovalMode.auto_review, base_instructions=None, config=None, cwd=None, developer_instructions=None, ephemeral=None, model=None, model_provider=None, sandbox: Sandbox | None = None) -> Awaitable[AsyncThread]
  • thread_archive(thread_id: str) -> Awaitable[ThreadArchiveResponse]
  • thread_unarchive(thread_id: str) -> Awaitable[AsyncThread]
  • models(*, include_hidden: bool = False) -> Awaitable[ModelListResponse]

Async context manager:

async with AsyncCodex() as codex:
    ...

Login handles

ChatgptLoginHandle / AsyncChatgptLoginHandle

  • login_id: str
  • auth_url: str
  • wait() -> AccountLoginCompletedNotification
  • cancel() -> CancelLoginAccountResponse

Async handle methods return awaitables.

DeviceCodeLoginHandle / AsyncDeviceCodeLoginHandle

  • login_id: str
  • verification_url: str
  • user_code: str
  • wait() -> AccountLoginCompletedNotification
  • cancel() -> CancelLoginAccountResponse

Async handle methods return awaitables.

wait() consumes only the completion notification for its matching login attempt. API-key login completes synchronously and does not return a handle.

Thread / AsyncThread

Thread and AsyncThread share the same shape and intent.

Thread

  • run(input: str | Input, *, approval_mode=None, cwd=None, effort=None, model=None, output_schema=None, personality=None, sandbox: Sandbox | None = None, service_tier=None, summary=None) -> TurnResult
  • turn(input: str | Input, *, approval_mode=None, cwd=None, effort=None, model=None, output_schema=None, personality=None, sandbox: Sandbox | None = None, service_tier=None, summary=None) -> TurnHandle
  • read(*, include_turns: bool = False) -> ThreadReadResponse
  • set_name(name: str) -> ThreadSetNameResponse
  • compact() -> ThreadCompactStartResponse

AsyncThread

  • run(input: str | Input, *, approval_mode=None, cwd=None, effort=None, model=None, output_schema=None, personality=None, sandbox: Sandbox | None = None, service_tier=None, summary=None) -> Awaitable[TurnResult]
  • turn(input: str | Input, *, approval_mode=None, cwd=None, effort=None, model=None, output_schema=None, personality=None, sandbox: Sandbox | None = None, service_tier=None, summary=None) -> Awaitable[AsyncTurnHandle]
  • read(*, include_turns: bool = False) -> Awaitable[ThreadReadResponse]
  • set_name(name: str) -> Awaitable[ThreadSetNameResponse]
  • compact() -> Awaitable[ThreadCompactStartResponse]

run(...) is the common-case convenience path. It accepts plain strings, starts the turn, consumes notifications until completion, and returns a small result object with:

  • id: str
  • status: TurnStatus
  • error: TurnError | None
  • started_at: int | None
  • completed_at: int | None
  • duration_ms: int | None
  • final_response: str | None
  • items: list[ThreadItem]
  • usage: ThreadTokenUsage | None

final_response is None when the turn finishes without a final-answer or phase-less assistant message item.

Use turn(...) when you need low-level turn control (stream(), steer(), interrupt()) before collecting the turn result.

Sandbox

Use sandbox= consistently on thread lifecycle methods and turns:

from openai_codex import Codex, Sandbox

with Codex() as codex:
    thread = codex.thread_start(sandbox=Sandbox.workspace_write)
    result = thread.run("Review the diff only.", sandbox=Sandbox.read_only)

Presets:

  • Sandbox.read_only: read files without allowing writes.
  • Sandbox.workspace_write: the normal default for projects with a recorded trust decision; read files and write inside the workspace and configured writable roots.
  • Sandbox.full_access: run without filesystem access restrictions.

When sandbox= is omitted, Codex uses its configured default. A sandbox passed to run(...) or turn(...) applies to that turn and subsequent turns.

TurnHandle / AsyncTurnHandle

TurnHandle

  • steer(input: str | Input) -> TurnSteerResponse
  • interrupt() -> TurnInterruptResponse
  • stream() -> Iterator[Notification]
  • run() -> TurnResult

Behavior notes:

  • stream() and run() consume only notifications for their own turn ID
  • one Codex instance can stream multiple active turns concurrently

AsyncTurnHandle

  • steer(input: str | Input) -> Awaitable[TurnSteerResponse]
  • interrupt() -> Awaitable[TurnInterruptResponse]
  • stream() -> AsyncIterator[Notification]
  • run() -> Awaitable[TurnResult]

Behavior notes:

  • stream() and run() consume only notifications for their own turn ID
  • one AsyncCodex instance can stream multiple active turns concurrently

Inputs

@dataclass class TextInput: text: str
@dataclass class ImageInput: url: str
@dataclass class LocalImageInput: path: str
@dataclass class SkillInput: name: str; path: str
@dataclass class MentionInput: name: str; path: str

InputItem = TextInput | ImageInput | LocalImageInput | SkillInput | MentionInput
Input = list[InputItem] | InputItem
RunInput = Input | str

Use ImageInput with a base64-encoded data:image/... URL. HTTP and HTTPS image URLs are deprecated; download remote images and pass their local paths with LocalImageInput instead.

Use a plain str as shorthand for TextInput(...) anywhere a turn input is accepted: thread.run("..."), thread.turn("..."), and turn.steer("...").

Public Types

The SDK wrappers return and accept public Codex protocol models wherever possible:

from openai_codex.types import (
    Account,
    AccountLoginCompletedNotification,
    CancelLoginAccountResponse,
    CancelLoginAccountStatus,
    GetAccountResponse,
    ThreadReadResponse,
    Turn,
    TurnStatus,
)

Notifications and generated models

Known notifications have typed Notification.payload values, including authentication recovery, thread queue/project changes, thread reversion, and realtime item updates. The Notification.payload type covers every registered event. Unknown methods and payloads that fail validation still produce UnknownNotification, with the raw data in .params. When an event gains a typed payload, read its named fields instead of .params.

Returned models include the current CLI's thread metadata, richer turn errors, and functionCallOutput history items. Code that imports generated HookMetadata directly must access the handler through .root, inspect its handler_type, and then read the fields for that handler. For example, only a "command" handler has a command field. This reflects the app-server's separate command, MCP tool, prompt, and agent hook variants.

Retry + errors

from openai_codex import (
    retry_on_overload,
    JsonRpcError,
    MethodNotFoundError,
    InvalidParamsError,
    ServerBusyError,
    is_retryable_error,
)
  • retry_on_overload(...) retries transient overload errors with exponential backoff + jitter.
  • is_retryable_error(exc) checks if an exception is transient/overload-like.

Example

from openai_codex import Codex

with Codex() as codex:
    thread = codex.thread_start(model="gpt-5.4", config={"model_reasoning_effort": "high"})
    result = thread.run("Say hello in one sentence.")
    print(result.final_response)