OpenAI Responses API skill for Codex

Typed output items, deliberate state, clean streams. Codex writes Responses API code that walks output items by type, keeps reasoning items when replaying history, and resends instructions on every turn. It picks one conversation-state strategy on purpose, rewrites Chat Completions shapes with a checklist, and treats truncated responses as failures instead of parsing half an answer.

  • AI & LLM Engineering
  • $openai-responses-api
  • 181 lines
  • 2 files
  • ~2,100 tokens
  • MIT
  • Updated
  • Editor's pick

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- openai-responses-api

Installs to ~/.agents/skills/openai-responses-api/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $openai-responses-api, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Builds and migrates OpenAI integrations on the Responses API (POST /v1/responses) — instructions and input items, output item parsing, previous_response_id and Conversations state, store=false with reasoning replay, built-in tools, typed streaming events and compaction. Use when writing new OpenAI API code, moving a Chat Completions integration to Responses, debugging missing context or lost reasoning between turns, or wiring streaming output into a server or UI.

Use it when

  • Starting a new integration against the OpenAI API
  • Porting Chat Completions code to /v1/responses
  • Multi-turn context or reasoning quality degrades between turns
  • Adding built-in tools such as web search or file search

Use something else when

How to check the result

A fixture test covering message, reasoning, function_call and refusal items passes, and a live two-turn smoke call shows turn 2 using turn 1 context with expected input token growth.

The mistake it prevents

Assuming instructions carry over with previous_response_id; they do not, so the persona silently disappears on the second turn.

~/.agents/skills/openai-responses-api/SKILL.md

--- name: openai-responses-api description: Builds and migrates OpenAI integrations on the Responses API (POST /v1/responses) — instructions and input items, output item parsing,… license: MIT ---

Done well means: every call goes through one thin client wrapper, conversation state is owned on purpose (server-side or replayed, never both), the code walks output items by type instead of assuming a single message, and streaming handlers branch on event type.

Before writing code

  1. Read AGENTS.md for the package manager, env-var conventions and test command.
  2. Find the current integration: grep -rnE "chat\.completions|responses\.create|openai\(" src/ app/ lib/ 2>/dev/null.
  3. Check the SDK version in the lockfile (openai for Python or Node). Responses helpers such as output_text and responses.parse need a recent SDK; upgrade first if the version predates them. Installing packages needs network, which may require approval in the Codex sandbox.
  4. Never hard-code the model in call sites. Read it from config (OPENAI_MODEL) and check the models page for the current recommendation before choosing a default.

Request shape

import os
from openai import OpenAI

client = OpenAI()  # reads OPENAI_API_KEY
MODEL = os.environ["OPENAI_MODEL"]  # choose from the models page; pin a snapshot in prod

resp = client.responses.create(
    model=MODEL,
    instructions="You are a terse release-notes writer.",  # system-level guidance
    input="Summarize: fixed login loop on Safari; added CSV export.",
    max_output_tokens=800,
)
print(resp.output_text)  # SDK helper: concatenated text of message items
import OpenAI from "openai";
const client = new OpenAI();
const resp = await client.responses.create({
  model: process.env.OPENAI_MODEL!,
  instructions: "You are a terse release-notes writer.",
  input: "Summarize: fixed login loop on Safari; added CSV export.",
});
console.log(resp.output_text);

input is a string or a list of items: {"role": "user"|"assistant"|"developer", "content": ...}, function_call_output, images ({"type": "input_image", "image_url": ...}) and files.

Reading output

resp.output is a list of typed items. Handle at least these:

item.typeMeaningWhat to do
messageAssistant text (content parts output_text or refusal)Render; check for refusal
reasoningReasoning summary / encrypted stateKeep for replay; do not show raw
function_callYour tool call (name, arguments, call_id)Execute, return function_call_output
web_search_call, file_search_call, image_generation_call, mcp_call, …Built-in tool activityLog; surface citations

Also check resp.status: incomplete with incomplete_details.reason == "max_output_tokens" means truncated output — raise or retry with a higher limit, never parse it as complete.

State: pick exactly one strategy

NeedStrategyNotes
Simple chat, data may be storedprevious_response_id=prev.id (with store=True, the default)Resend instructions every turn; they are not inherited. Prior tokens are still billed as input. Stored responses expire after 30 days.
Durable thread across devices/sessionsConversations API: conv = client.conversations.create() then conversation=conv.idConversation items are not subject to the 30-day response TTL.
Zero data retention, compliance, or full controlstore=False and replay the full output list yourselfKeep reasoning items (they carry encrypted content) or reasoning models lose their chain of thought between turns.
history = [{"role": "user", "content": "Plan a DB migration for orders."}]
r1 = client.responses.create(model=MODEL, input=history, store=False)
history += r1.output              # replay ALL items, including reasoning
history.append({"role": "user", "content": "Now add a rollback step."})
r2 = client.responses.create(model=MODEL, input=history, store=False)

In Node, convert output items before replay with the SDK helper rather than spreading raw objects if your SDK version provides one; otherwise map fields explicitly.

Never mix strategies in one thread (for example previous_response_id plus a replayed history) — the model sees duplicated turns.

Long threads: use server-side compaction (context_management with a compaction threshold) or the standalone compact endpoint instead of hand-truncating; check the conversation-state guide for the current parameter shape before relying on it.

Built-in tools

resp = client.responses.create(
    model=MODEL,
    input="What changed in the latest PostgreSQL minor release?",
    tools=[{"type": "web_search"}],
)

Built-ins include web_search, file_search (needs a vector store id), code_interpreter, image_generation and remote MCP servers. Function tools use the flat shape {"type": "function", "name": ..., "parameters": ..., "strict": True} — not the nested {"function": {...}} shape from Chat Completions. For tool loops use the openai-function-calling skill.

Streaming

stream = client.responses.create(model=MODEL, input="Write a haiku about caches.", stream=True)
for event in stream:
    match event.type:
        case "response.output_text.delta":
            print(event.delta, end="", flush=True)
        case "response.function_call_arguments.done":
            pass  # arguments complete for one call
        case "response.completed":
            final = event.response          # full response with usage
        case "error":
            raise RuntimeError(event)

Rules: branch on event.type; ignore unknown types (new ones get added); take usage and final status from response.completed, not from accumulated deltas; propagate client disconnects by closing the stream so you stop paying for tokens.

Migrating from Chat Completions

  • messages → instructions (system text) + input (remaining turns)
  • choices[0].message.content → resp.output_text or walk resp.output
  • response_format → text={"format": {...}}
  • Function tools: flatten {"type":"function","function":{...}} to {"type":"function","name":...}
  • Tool results: {"role":"tool","tool_call_id":...} → {"type":"function_call_output","call_id":...,"output":...}
  • max_tokens / max_completion_tokens → max_output_tokens
  • n is gone: issue separate requests for multiple candidates
  • Stream handlers rewritten for typed events (no choices[0].delta)
  • Decide store explicitly (Responses defaults to True; Chat Completions defaults store to false)
  • usage.prompt_tokens → usage.input_tokens; cached tokens at usage.input_tokens_details.cached_tokens

Migrate one call site at a time behind the wrapper, compare outputs on a fixed prompt set, then delete the old path.

Gotchas

  • instructions do not carry over with previous_response_id. Missing persona on turn 2 is almost always this.
  • Dropping reasoning items when replaying with store=False degrades multi-turn and tool-use quality silently.
  • output_text is an SDK convenience. Raw HTTP clients must walk output[].content[].
  • A response can contain several function_call items; handle all before the next call.
  • temperature/top_p may be unsupported on reasoning models; use reasoning={"effort": ...} and text={"verbosity": ...} where the model supports them. Check the model page.
  • Set safety_identifier (a hashed user id) for end-user traffic instead of the old user field.
  • Do not log full prompts or outputs that contain user PII; log response ids and usage.

Verification

  1. Unit-test the wrapper with a recorded response fixture covering message, reasoning, function_call and refusal items.
  2. Run one live smoke call (needs network and a key; ask before spending tokens in CI): python -c "from app.llm import ask; print(ask('ping'))".
  3. For multi-turn changes, assert turn 2 references turn 1 content, and that usage.input_tokens grows as expected.
  4. Grep that no old shapes remain: grep -rn "chat.completions\|tool_call_id\|response_format" src/.

Output contract

When reporting the change, include:

**Endpoint:** Responses API, model from `OPENAI_MODEL` (currently <value>)
**State strategy:** previous_response_id | conversation | store=false replay — and why
**Call sites migrated:** <list with paths>
**Streaming:** events handled: <list>
**Verified:** <tests run, smoke call result, token usage before/after>

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "OpenAI Responses API"
  short_description: "Typed output items, deliberate state, clean streams"
  default_prompt: "$openai-responses-api Migrate our chat.completions calls in src/llm to the Responses API"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem