Responses API (OpenAI Codex)
OpenAI-compatible Responses API for the Codex CLI and other Responses-format clients.
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.
model
string
Required
404.
input
string | array
Required
input_text, input_image and input_file parts; an input_file carries a .txt or .csv of up to 1 MB (see Text and CSV files).
store
boolean
reasoning
object
{"effort":"low"|"medium"|"high"} to set reasoning depth, or {"effort":"none"} (or the synonym "minimal") to skip thinking entirely. Pendra also accepts {"max_tokens":4096} here to set a thinking budget in tokens: when it is used up the thinking ends and the model goes on to its answer, and max_output_tokens is then kept for the answer alone. Leave it out and a default budget of 8,192 tokens applies on models whose thinking can be ended early. max_tokens must be a whole number, 0 or more; anything else is rejected (422, or 400 on an encrypted request). With no max_output_tokens, the reply stops at 32,768 tokens, thinking included. See Thinking.
temperature
number
default: the model's recommended value, else 1.0
0 to 2. At 0 the model always picks its most likely next token, for the most focused, repeatable answers. Leave it out to use the value the model's authors recommend.
top_p
number
default: the model's recommended value, else 1.0
top_p are considered. Leave it out to use the value the model's authors recommend.
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.
When Pendra has something to tell you about a reply, such as the thinking
having reached its budget, the response also carries a
pendra object with a notice: a stable
code and a readable message. When streaming it
is on the response in the final response.completed event.
OpenAI SDKs ignore the extra field. The codes are listed under
Notices.
A non-streaming reply also says which of your workers ran it and how long it
took: the X-Worker-Id, X-Worker-Name and
Server-Timing headers, and a
pendra field in the body that OpenAI clients ignore:
"pendra": {"worker": {"id": "wrk-1a2b3c4d", "name": "london-gpu-01", "version": "3.115.1"},
"request_id": "3f1c8a2e-5b7d-4e19-9c0a-6d2f8b1e4a73",
"timing": {"ttft_ms": 115, "tokens_per_second": 58.34, "queue_wait_ms": 16600}}
timing is the request's latency and throughput, the same figures
as the Server-Timing header. Read them from the body: a reply
slow enough that Pendra keeps the connection alive
is sent without that header. Streamed replies don't carry the
pendra field. They name the worker the request was sent to in
the X-Worker-Id and X-Worker-Name headers when one
is free straight away; a stream that had to wait for a busy worker starts
without them. See
Which worker served the request
and Timing for what each field
means.
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. The same goes, without a pin, when none of your workers is online,
when none of your connected workers is new enough for this endpoint, or when
an encrypted request is sealed to a key none of your workers holds any more
— each a 503 with a Retry-After header, the last
with X-Pendra-Error-Code: no_private_inference_worker (fetch
your organisation's key again and retry). A worker that's merely busy 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.