Skip to content

OpenAI-Compatible SDKs

Codex Pooler provides narrow OpenAI-compatible /v1 support for selected SDK routes. It translates supported requests into Codex-compatible work, then sends them through the same Pool routing, limit checks, account selection, and accounting path used by Codex backend clients.

It doesn’t provide full OpenAI API parity.

For a short citable summary, see OpenAI-compatible Codex gateway.

OpenAI-compatible means selected SDK request shapes can use a /v1 base URL and a Pool API key. Codex Pooler still routes the request through Codex account Pools, not through a separate OpenAI engine. Unsupported OpenAI API areas remain unsupported, including embeddings, batches, fine-tuning, moderation, response retrieve/cancel/delete, image variations, OpenAI Responses remote MCP tool definitions, and OpenAI Realtime SDK routes.

Use the /v1 base URL and a Pool API key:

Base URL:
https://codex-pooler.example.com/v1
Authorization:
Bearer <pool-api-key>

For local setup, use http://localhost:4000/v1.

Use service_tier: "priority" for priority processing. fast is an accepted equivalent request spelling, but priority is the canonical spelling for new configuration. On /v1, Codex Pooler translates supported OpenAI request and response shapes while preserving any projected provider service_tier value in its literal provider vocabulary. Codex backend relay routes under /backend-api/codex preserve provider bytes, frames, and service-tier vocabulary unchanged.

ultrafast is a separate tier, not an alias for fast or priority. Use service_tier: "ultrafast" only with direct /v1/responses requests when the selected model metadata advertises it:

response = client.responses.create(
model="your-model-id",
input="Your request input.",
service_tier="ultrafast",
)

JSON, SSE, and narrow Responses WebSocket requests preserve a returned ultrafast tier literally. POST /v1/chat/completions rejects ultrafast. Provider availability, access, and price control whether the tier can be used; model metadata advertisement is not an entitlement or price promise.

Use the dedicated setup pages when configuring an agent or editor that has its own provider shape:

import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["CODEX_POOLER_API_KEY"],
base_url="https://codex-pooler.example.com/v1",
)
response = client.responses.create(
model="gpt-5.6-terra",
input="Write a short setup confirmation.",
)
print(response.output_text)
import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.CODEX_POOLER_API_KEY,
baseURL: "https://codex-pooler.example.com/v1",
});
const response = await client.responses.create({
model: "gpt-5.6-terra",
input: "Write a short setup confirmation.",
});
console.log(response.output_text);
import { createOpenAI } from "@ai-sdk/openai";
import { generateText } from "ai";
const pooler = createOpenAI({
apiKey: process.env.CODEX_POOLER_API_KEY,
baseURL: "https://codex-pooler.example.com/v1",
});
const { text } = await generateText({
model: pooler.responses("gpt-5.6-terra"),
providerOptions: {
openai: {
promptCacheOptions: { mode: "explicit", ttl: "30m" },
},
},
messages: [
{
role: "system",
content: "Keep setup guidance concise.",
providerOptions: {
openai: { promptCacheBreakpoint: { mode: "explicit" } },
},
},
{ role: "user", content: "Write a short setup confirmation." },
],
});
console.log(text);

Codex Pooler accepts these OpenAI cache controls as public input. For account-backed requests, it adapts the explicit controls automatically before upstream dispatch while preserving prompt_cache_key independently from Pool routing affinity. GPT-5.6 model ids are passed through to the Pool catalog and assignment policy; naming one here does not guarantee that every Pool has an eligible account for that model.

For Vercel AI SDK programmatic tools, set providerOptions.openai.store: false on every generation that participates in the tool loop:

const result = await generateText({
model: pooler.responses("gpt-5.6-terra"),
providerOptions: {
openai: { store: false },
},
// Your programmatic tool and tool-choice configuration goes here.
prompt: appPrompt,
});

Vercel chooses complete stateless replay or a stored item_reference before the request reaches Codex Pooler. store: false is therefore required for the complete stateless replay path. Independently, Codex Pooler forces upstream stream: true and store: false.

This is narrow, closed-world compatibility, not general OpenAI programmatic-tool or Responses parity. Supported shapes are program and program_output, caller-enabled function_call and function_call_output, the type-only programmatic_tool_calling tool and tool choice, plus allowed_callers and map-only output_schema. Reference-only and ordinary stored-response continuations are not supported. Codex Pooler does not locally persist raw program code, results, schemas, or identifiers. A request accepted by the gateway can still be rejected when its selected upstream model or account lacks the required capability.

Responses function_call_output replay has two closed forms over HTTP and the narrow Responses websocket bridge. A paired item requires a nonblank call_id; its existing output form and paired-only legacy result form are unchanged. A named standalone item requires a nonblank name and an output field, permits call_id only when omitted or null, and permits namespace only when omitted, null, or a nonblank string. Blank or non-string values and standalone result reject before dispatch. Classification and debug summaries remain metadata-only. This does not expand general Responses parity or prove provider-live acceptance.

OpenAI Responses history may include completed shell_call and shell_call_output items from an earlier response. Codex Pooler accepts this closed-key replay subset, including direct or program callers, local or container environment metadata, in_progress, completed, or incomplete status, and empty command or output arrays where the shape permits them. It forwards accepted history for stateless replay and semantic tool-output continuations without requiring call/output pairing or a particular item order.

This is history forwarding only. Codex Pooler does not execute shell commands or accept a top-level shell tool declaration. local_shell_call history and remote MCP tool definitions remain unsupported. It also does not implement SDK-local command-index accumulation or claim general hosted-tool parity.

Responses SSE and the narrow Responses websocket preserve the five hosted-shell relay event types: command added, command delta, command done, output-content delta, and output-content done. Existing public sequence normalization and the websocket stream_id behavior are the only permitted relay adaptations.

Commands and shell output remain transient request data. They are not persisted or rendered in request metadata, logs, or operator views.

For a previous_response_id tool continuation, a native reasoning replay item may include content with reasoning_text parts. Codex Pooler preserves that accepted reasoning content for the upstream continuation. Stateless replay drops reasoning items before dispatch instead.

Assistant-history replay may include output_text URL citations. Codex Pooler accepts only ordered url_citation annotations with exactly type, start_index, end_index, url, and title; it preserves accepted values, explicit empty annotation lists, and omission exactly. Malformed or unsupported annotation shapes are rejected before dispatch rather than filtered.

GET /v1/models may include context_length for clients that probe OpenAI-compatible model lists, such as Hermes. The official OpenAI SDK request APIs and Vercel AI SDK generation APIs do not expose Codex model-catalog context controls. Use their output-budget fields only when your application needs one: max_output_tokens in OpenAI Responses, max_completion_tokens in Chat Completions, and maxOutputTokens at the Vercel AI SDK layer. Codex Pooler’s public /v1/responses rejects context_management, and direct POST /v1/responses/compact remains unsupported. Explicit compaction is available on the normal /v1/responses route.

Provider clients that expose an OpenAI compaction option can use the normal /v1/responses route. Vercel AI SDK serializes the required trigger with providerOptions.openai.compactionTrigger: true in @ai-sdk/openai 4.0.42 or later:

import {
createOpenAI,
type OpenAILanguageModelResponsesOptions,
} from "@ai-sdk/openai";
import { generateText } from "ai";
const pooler = createOpenAI({
apiKey: process.env.CODEX_POOLER_API_KEY,
baseURL: "https://codex-pooler.example.com/v1",
});
const result = await generateText({
model: pooler.responses("gpt-5.6-terra"),
providerOptions: {
openai: {
store: false,
compactionTrigger: true,
} satisfies OpenAILanguageModelResponsesOptions,
},
prompt: "Compact the visible conversation context.",
});
const compaction = result.content.find(
(part) => part.type === "custom" && part.kind === "openai.compaction",
);

The serialized request must contain visible input followed by exactly one final {"type":"compaction_trigger"} item. Non-terminal, duplicate, trigger-only, hidden-only, or otherwise malformed placement returns an OpenAI-shaped 400 invalid_request on input before upstream dispatch.

Successful non-streaming HTTP returns a completed Responses JSON object containing the normalized compaction item. Public SSE emits response.output_item.done, then response.completed, then [DONE]. Narrow Responses websocket completion emits the same two Responses events without the HTTP [DONE] sentinel. Direct POST /v1/responses/compact remains unsupported.

If upstream compact output is malformed JSON or does not contain nonblank encrypted compaction content, Codex Pooler returns a sanitized 502 invalid_compaction_response. Other provider failures follow the public error rules documented below.

When a compaction turn returns an encrypted type: "compaction" output item, submit that item unchanged at the start of the next POST /v1/responses input, followed by the new user input. Start a new chain by omitting previous_response_id; the compact response envelope id is not a replay anchor.

The stable public replay fields are type, a nonblank opaque encrypted_content string, and optional id. The id key may be absent, a string, or explicitly null, and its presence is preserved. JSON, SSE, and narrow Responses websocket surfaces return the same normalized item. Native-only metadata and unknown fields are removed from output; unknown replay fields or malformed values are rejected before upstream dispatch. Treat encrypted_content as opaque and do not log or persist it in application telemetry.

The OpenAI-compatible /v1 surface supports or translates selected routes only:

  • GET /v1/models
  • POST /v1/responses
  • GET /v1/responses, narrow Responses websocket compatibility only
  • POST /v1/chat/completions
  • GET /v1/usage
  • GET /v1/files
  • POST /v1/files
  • GET /v1/files/:file_id
  • POST /v1/audio/transcriptions
  • POST /v1/images/generations
  • POST /v1/images/edits

The /v1 surface is compatibility over Codex routing, not a separate OpenAI engine. Supported requests still require a Pool API key and a Pool with eligible upstream capacity for the requested model.

POST /v1/audio/transcriptions accepts gpt-transcribe as a caller alias. The gateway uses the fixed canonical backend identity gpt-4o-transcribe. The alias is not a model-list entry or a model-discovery guarantee.

import fs from "node:fs";
import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.CODEX_POOLER_API_KEY,
baseURL: "http://localhost:4000/v1",
});
const transcription = await client.audio.transcriptions.create({
file: fs.createReadStream("audio.mp3"),
model: "gpt-transcribe",
keywords: ["example", "example"],
languages: ["en", "it"],
});
console.log(transcription.text);
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["CODEX_POOLER_API_KEY"],
base_url="http://localhost:4000/v1",
)
with open("audio.mp3", "rb") as audio_file:
transcription = client.audio.transcriptions.create(
file=audio_file,
model="gpt-transcribe",
keywords=["example", "example"],
languages=["en", "it"],
)
print(transcription.text)

keywords and languages are optional nonempty string lists. Empty lists are omitted. Nonempty lists retain caller order and duplicate entries. Responses omit language-detection fields. This documents the accepted request shape only, not transcription quality, model discovery, or general Audio API coverage.

POST /v1/responses lifts system and developer input-message text into top-level instructions before dispatching to Codex-compatible work. Streaming /v1/responses and /v1/chat/completions return early upstream terminal errors as the first public SSE event or data chunk, without synthetic assistant/success prefixes. Non-streaming failures remain OpenAI-shaped JSON errors.

Streaming /v1/responses clients must also handle a terminal type: "error" SSE event whose flat fields and nested error object both carry the sanitized server_error code.

Streaming /v1/chat/completions clients must separately handle a terminal data: {"error":{...}} chunk with nested server_error fields and no top-level type; no [DONE] chunk follows that terminal.

For public SSE, an absent, blank, or whitespace-only event: label is treated as absent before the label is compared with the JSON type. A nonblank mismatch is still rejected. Ordinary incomplete POST /v1/responses SSE blocks are capped at 8 MiB so single large provider events, such as reasoning items carrying encrypted content, can finish decoding; structurally recognizable terminal candidates may retain up to 64 MiB so a large terminal split across upstream chunks can finish decoding. Crossing the applicable cap sends one bounded sanitized type: "error" event, relays none of that source block, and drops later source frames.

On narrow GET /v1/responses websocket compatibility, malformed JSON and JSON values that are not objects are ignored without consuming a sequence number or creating a local terminal. Native backend websocket behavior is unchanged.

Narrow GET /v1/responses websocket response.create accepts an optional stream_id only when it is a 1 through 256 byte string matching ^[A-Za-z0-9_.-]+$. A valid accepted ID is echoed on every attributable Open Responses server event and is stripped before upstream dispatch. It is transient socket-turn state and is not stored in request metadata, accounting, logs, or telemetry.

Requests with the same ID are FIFO. Different valid IDs are accepted and echoed, but Codex Pooler conservatively serializes public turns per connection; it does not promise cross-ID concurrency or fairness. previous_response_id controls conversation lineage independently. REST POST /v1/responses, native backend WebSockets, Chat, compact, and batches do not accept this field.

When the Codex backend returns a genuine terminal error, Codex Pooler preserves its trimmed error.code only when the value is at most 80 bytes and matches ^[A-Za-z0-9_.-]+$. Other code values are redacted to upstream_error. A surviving code may be a Codex-backend value that is not part of the OpenAI platform ResponseError enum.

Upstream error messages are replaced with upstream request failed, and upstream error types are replaced with server_error. Clients must therefore treat error.code as an open string: handle the values they understand and keep an unknown-code fallback rather than validating against the platform enum.

Public relays construct each response.failed envelope from a named-field projection. Unknown event, response, error, and usage siblings are excluded. The response identifier is retained only when it is a valid resp_ identifier; otherwise it becomes resp_failed. Usage is either null or a bounded projection of the declared token counters. Content-bearing response fields are empty or null: output and tools are empty arrays, output_text is an empty string, and instructions, metadata, temperature, and top_p are null.

Top-level and nested errors are sanitized independently and are never copied between locations. A safe code remains unchanged at its original location, while every invalid code becomes upstream_error; message and type remain the fixed upstream request failed and server_error values.

One narrow exception applies to misalignment_policy_violation. An eligible direct 400 or 403, or an exact terminal SSE or websocket failure, is health-neutral and non-retryable. Public clients receive the exact code, invalid_request_error, and the nonblank provider message when available, or a fixed safe fallback when it is blank. The error never includes a provider param, body, or sibling fields. Durable accounting and logs retain only the exact code, fixed accounting text, and bounded facts. Every other provider error continues to use the generic redaction above.

Schema-strict clients that enforce the platform enum cannot accept every relayed Codex-backend code. In openai-python, leave _strict_response_validation at its default false when using this compatibility surface.

Ordinary OpenAI Responses incomplete terminals are preserved. If an upstream returns response.incomplete with status: "incomplete" for output limits or content filtering and no embedded error, /v1/responses streaming and websocket clients receive that incomplete terminal instead of a synthetic failure. Error-coded incomplete terminals, such as context overflow or stale continuation anchors, are still returned through the sanitized failure path.

Accepted POST /v1/responses tool definitions are narrow. OpenAI Responses remote MCP tool definitions are rejected before dispatch.

In Full serving mode, direct POST /v1/responses and narrow Responses websocket response.create accept an allowed_tools choice only when every member is backed by a direct top-level declaration. Named function and custom members must match the declared kind and name. The only type-only built-ins are programmatic_tool_calling, web_search_preview, web_search, and image_generation, each declared at the top level. Caller order and repeated members are preserved.

This is a narrow declaration-backed contract, not broad OpenAI tool parity. It doesn’t cover Chat, backend Responses, namespaces, additional tools, deferred tools, aliases, Realtime, or remote MCP. Invalid or undeclared Full choices fail before dispatch. Lite rejects any map-shaped allowed_tools choice with the existing unsupported_parameter error on tool_choice.

Remote MCP remains unsupported in Responses. A top-level MCP declaration is a tools error, while an MCP member inside allowed_tools is a tool_choice error. The separate /mcp endpoint is operator metadata access, not a Responses remote MCP bridge.

web_search.filters accepts only allowed_domains and blocked_domains. Each supplied field is a list of 1 through 100 nonblank strings without leading- whitespace, case-insensitive HTTP(S) schemes. Both fields can appear together. Codex Pooler forwards accepted values unchanged, including their order, case, duplicates, and bytes.

external_web_access is optional. Codex Pooler guarantees local validation and forwarding of this accepted shape only. Web search availability and any allow-list or block-list enforcement can vary by selected upstream model and account.

Per-Pool request compression can apply to POST /v1/responses, POST /v1/chat/completions, and narrow Responses websocket response.create traffic. It is transparent to SDK clients and does not require a client flag. Operators enable it with the Pool setting request_compression_enabled.

The feature is request-side only. Codex Pooler may compress upstream-bound Responses tool-output payloads before dispatch, but it does not store raw outputs, does not store upstream response bodies, and does not implement CCR/retrieval. Public /v1/responses/compact remains unsupported.

If compression is enabled but savings do not appear, check the request log’s payload_compression status and reason. The UI prefers saved token count and token savings percent when local token counts are available, then falls back to saved bytes and byte savings percent.

These /v1 routes are unsupported and may return deterministic OpenAI-shaped unsupported endpoint errors when explicitly routed:

  • POST /v1/responses/compact
  • GET /v1/files/:file_id/content, after ownership checks
  • DELETE /v1/files/:file_id, after ownership checks
  • POST /v1/images/variations
  • POST /v1/content_provenance_checks, deliberately routed to deterministic OpenAI-shaped unsupported_endpoint
  • POST /v1/embeddings
  • POST /v1/batches
  • POST /v1/moderations
  • POST /v1/fine_tuning/jobs
  • GET /v1/responses/:response_id
  • POST /v1/responses/:response_id/cancel
  • DELETE /v1/responses/:response_id
  • /v1/realtime and OpenAI Realtime SDK websocket or session routes

GET /v1/responses is narrow Responses websocket compatibility, not /v1/realtime support. OpenAI Realtime SDK websocket and session routes are not supported.

Within POST /v1/responses, OpenAI Responses remote MCP tool definitions are unsupported. A top-level tools entry with type: "mcp", or an input item with type: "additional_tools" whose tools list contains type: "mcp", is rejected before upstream dispatch with an OpenAI-shaped invalid_request error.

The operator MCP endpoint is rooted at /mcp, not under /v1. It uses operator-owned MCP bearer tokens and returns metadata only.

The root /mcp endpoint is not a /v1/responses remote MCP bridge and is not invoked by Responses tools[type=mcp] definitions.

MCP URL:
https://codex-pooler.example.com/mcp
Authorization:
Bearer <operator-mcp-token>

Don’t use Pool API keys, browser sessions, cookies, query tokens, invite tokens, upstream tokens, or custom headers as MCP authentication.

Can I use the official OpenAI SDK with Codex Pooler?

Section titled “Can I use the official OpenAI SDK with Codex Pooler?”

Yes, for selected SDK routes. Set the SDK base_url or baseURL to https://codex-pooler.example.com/v1, use a Pool API key as the bearer credential, and keep requests on supported route shapes such as responses, chat completions, models, usage, files, audio transcription, and image generation or edits.

No. Codex backend-compatible clients should use /backend-api/codex. The /v1 surface is for selected OpenAI SDK-compatible clients and translates supported requests into Codex-compatible work.

No. /v1/realtime and OpenAI Realtime SDK websocket or session routes are unsupported. GET /v1/responses is narrow Responses websocket compatibility only, not OpenAI Realtime support.

No. POST /v1/responses/compact returns a deterministic OpenAI-shaped unsupported_endpoint error. Explicit compaction is supported on normal /v1/responses requests when the client appends exactly one final compaction_trigger after visible input. Backend compact compatibility remains available under /backend-api/codex.

No. /v1 uses Pool API keys for runtime work. /mcp uses operator-owned MCP bearer tokens for metadata-only lookup. Keep those credentials separate in client configuration and storage.