Responses API
The OpenAI Responses shape, translated at the edge onto the same order pipeline as chat. A request here is priced, queued, charged, logged and replayed exactly as a chat request; only the wire differs. It keeps no state between calls, so each turn resends its input.
Create a response
curl https://api.openfill.ai/v1/responses \ -H "Authorization: Bearer $OPENFILL_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model": "placeholder-large", "input": "hi", "max_price": 0.10}'Request body
The Responses fields first, then the OpenFill extensions, which carry the same names and rules as on chat. A field absent from this table is forwarded to the model untouched.
| Field | Type | Default | Meaning |
|---|---|---|---|
| model | string | the default model | A served model id or alias. |
| input | string or array | required | A string is one user message. An array holds message items with role user, assistant, system or developer and text parts, function_call items, function_call_output items and reasoning items, in conversation order. |
| instructions | string | none | Becomes the system message at the front. |
| tools | array | none | Function tools in the Responses shape, name and parameters at the top level. A web_search entry is dropped without error, since Codex sends one by default; every other hosted tool is refused. |
| tool_choice | string or object | auto | none, auto, required, or a named function. allowed_tools becomes auto or required from its mode; the restriction to the listed tools is passed to the model as a preference and is otherwise unenforced. |
| max_output_tokens | integer | the model's max output | The output cap, clamped down to the model's max output length. |
| temperature, top_p | number | none | 0 to 2, and 0 to 1. |
| text.format | object | none | json_object and json_schema become response_format for the model; text is the default. |
| reasoning.effort | string | none | Forwarded to the model as reasoning_effort. Summary settings are dropped. |
| parallel_tool_calls | boolean | none | Forwarded. |
| metadata | object | none | Up to 16 string pairs, echoed on the response and kept with the request log. |
| store | boolean | none | Accepted and echoed. Storage here follows the account's data storage setting; retention_days below is the per-request switch. |
| stream | boolean | false | Server-sent events, in the Responses event vocabulary. |
| max_price | number | key or account | The most you will pay, in USD per 1M input tokens at the reference mix. None anywhere is 400 missing_bid. The X-Max-Price header carries it for a tool with no body field. |
| price_lock | boolean | account setting | Bid the lower of the published quote and max_price, and start within the execute window or expire with 429 capacity_unavailable. |
| max_wait | integer | account setting | Seconds an unlocked order may wait. Alias expires_after; the X-Expires-After header carries it too. |
| retention_days | integer | account setting | Keep this request's data for fewer days. 0 keeps nothing durable. |
Refused with 400
| Sent | Why |
|---|---|
| previous_response_id, conversation | This endpoint keeps no state. Resend the full input each turn. |
| background: true | Use detached on POST /v1/chat/completions and poll the order. |
| prompt | Server-side prompt templates do not exist here; send the text in input. |
| truncation: auto | The input reaches the model unchanged, so it cannot be honoured. |
| detached: true | The stored result is chat-shaped, so the detached path is POST /v1/chat/completions. |
| input_image, input_file, input_audio parts | Text only. Nothing is charged. |
| item_reference and every hosted-tool item | They need server-side state or a tool the market does not run. |
| tools other than function | Only function tools run here; web_search is dropped rather than refused. |
Response
The Responses object, with every key the OpenAI reference shows present and null where it has no value. The id carries the order id, so the request can be found under GET /v1/orders/{id}.
{ "id": "resp_<order_id>", "object": "response", "created_at": 1756800000, "completed_at": 1756800042, "status": "completed", "error": null, "incomplete_details": null, "model": "placeholder-large", "output": [ {"id": "rs_<order_id>", "type": "reasoning", "status": "completed", "summary": [], "content": [{"type": "reasoning_text", "text": "..."}]}, {"id": "msg_<order_id>", "type": "message", "status": "completed", "role": "assistant", "content": [{"type": "output_text", "text": "...", "annotations": []}]}, {"id": "fc_<order_id>_0", "type": "function_call", "status": "completed", "call_id": "...", "name": "get_weather", "arguments": "{\"city\": \"Boston\"}"} ], "usage": {"input_tokens": 1234, "input_tokens_details": {"cached_tokens": 1000}, "output_tokens": 56, "output_tokens_details": {"reasoning_tokens": 0}, "total_tokens": 1290, "x_market": {"level_at_execution": 0.23, "bid": 0.5, "charged_usd": 0.0023}}, "store": false, "metadata": {}, ...}| Field | Meaning |
|---|---|
| status | completed on a normal finish; incomplete with incomplete_details.reason max_output_tokens when the cap stopped it. |
| output | A reasoning item when the model sent reasoning, then the message, then one function_call item per call, in that order. An item absent from the answer is absent from the list. |
| output[].content[].type | reasoning_text carries the raw chain of thought; Codex shows it only with show_raw_agent_reasoning on. |
| usage.input_tokens | Every prompt token, cached ones included; input_tokens_details.cached_tokens says how many were cached. |
| usage.x_market | The receipt: the price the order was charged at, what it bid, and the debit. |
| metadata, store | Echo the request, as do instructions, tools, tool_choice, text and reasoning. |
Streaming
200 and a text/event-stream body. Every frame carries an event: line, the same name in the JSON type, and a sequence_number that starts at 0 and rises by one on every event. There is no [DONE] sentinel: the body ends after the terminal event.
event: response.createdevent: response.in_progress <- also every 10 s of silence while the order waitsevent: response.output_item.addedevent: response.content_part.addedevent: response.output_text.delta <- one per fragmentevent: response.output_text.doneevent: response.content_part.doneevent: response.output_item.doneevent: response.completed <- carries the response object with usage and x_market| Case | Events |
|---|---|
| A function call | output_item.added with the call's name and call_id, function_call_arguments.delta per fragment, function_call_arguments.done, output_item.done. |
| Reasoning before the answer | A reasoning item with reasoning_text.delta events, closed before the message item opens. |
| The output cap stopped it | response.incomplete in place of response.completed, with incomplete_details.reason max_output_tokens. |
| An error after the stream opened | One response.failed event with response.error.code and message, then the body ends. The status stays 200 and nothing is charged. |
The queue comment lines the chat stream writes are kept, and every keepalive also sends a response.in_progress event, because Codex resets its idle timer only on a parsed event. Its default of five minutes therefore needs no change for a long wait.
Statuses
The same statuses, envelope and charging rules as chat. An Idempotency-Key works here and a replay comes back in this shape; a streaming replay is the whole event sequence at once.