Endpoints

Responses API (OpenAI Codex)

OpenAI-compatible Responses API for the Codex CLI and other Responses-format clients.

POST /v1/responses

Pendra implements the OpenAI Responses API at /v1/responses (also aliased at /api/v1/responses) so the OpenAI Codex CLI works without modification when pointed at Pendra. The Python and JavaScript examples use the official OpenAI SDKs.

Body application/json
model string required
Model ID. Must be a model your workers serve — see available models. An id Pendra doesn't serve returns 404.
input string | array required
The prompt or full conversation. This endpoint is stateless — send everything on each request.
store boolean
Accepted but has no effect — nothing is persisted server-side.
reasoning object
Control thinking on reasoning-capable models. Pass {"effort":"low"|"medium"|"high"} to set reasoning depth, or {"effort":"none"} (or the synonym "minimal") to skip thinking entirely. See Thinking.

previous_response_id is not supported and returns a 400.

Response

Pendra returns the OpenAI Responses envelope (see example). output is an array of items; each message item carries an array of content blocks. status is completed on a clean finish, or incomplete when the model hits a stop condition before exhausting max_output_tokens.

Prompt caching

Pendra caches prompts automatically. When you send follow-up turns of the same conversation, it reuses the part of the prompt it already processed instead of reprocessing it from scratch, which makes the reply start sooner. You don't have to do anything to switch this on.

usage.input_tokens_details.cached_tokens tells you how many of that request's input_tokens came from the cache — the same field OpenAI returns, so a cache hit rate of cached_tokens / input_tokens is comparable. It's always present, and 0 on a first, cold request. A higher rate means a faster first token on that request.

On a streamed response, the cache count lands on response.completed; the response.created event reports 0, because nothing is known about the prompt until the model has read it.

Pin a Pendra model

Codex hard-codes OpenAI model names like gpt-5-codex and gpt-5.5. Pendra serves open models under their own ids and won't substitute one for a name it doesn't recognise, so those requests come back as a 404 telling you to pin a model. Set model in your Codex config to an id from /models — see Integrations → Codex.

Streaming

The Responses API uses its own event taxonomy (response.output_text.delta, response.completed, etc.). Pendra emits these events from streamed chat completions; the Codex CLI consumes them directly.

event: response.created
data: {"type":"response.created","response":{"id":"resp_01HZ8b","object":"response","status":"in_progress","model":"qwen3.6:27b"}}

event: response.output_text.delta
data: {"type":"response.output_text.delta","item_id":"msg_01","output_index":0,"content_index":0,"delta":"Hello"}

event: response.completed
data: {"type":"response.completed","response":{"id":"resp_01HZ8b","status":"completed","usage":{"input_tokens":8,"output_tokens":2,"total_tokens":10,"input_tokens_details":{"cached_tokens":0}}}}

While the model is still reading a long prompt, Pendra sends an SSE comment line (: keep-alive) every 15 seconds so the connection isn't dropped as idle before the first event arrives. It carries no event and no data, so a Server-Sent Events client — the Codex CLI included — discards it; only hand-rolled parsers need to skip lines starting with :.

If you pin a worker with X-Pendra-Worker-Id and it can't serve the request — it doesn't exist, doesn't have the model, or is running a version too old for this endpoint — you get a normal HTTP error response instead of a stream, so check the status code before you start reading events. A worker that's merely busy, or not connected yet, still reports in-band as an error event once the stream has started. See Errors & rate limits.

Quick start

The full Codex setup — config file, env vars, and a model pin — lives in Integrations → Codex.