The Complete Guide to OpenAI’s New Responses API: How to Build Multi-Step AI Agents with Web Search, File Analysis, and Computer Use Capabilities

OpenAI Responses API 2026: The Definitive Guide to Building Multi-Step AI Agents with Web Search, File Analysis, and Secure Code Execution
Released alongside the groundbreaking Codex research paper on June 25, 2026, OpenAI’s innovative Responses API redefines how developers build intelligent, multi-step AI agents. This next-generation API empowers engineers, technical architects, and product innovators to create scalable AI systems that seamlessly integrate complex workflows—harnessing model reasoning, real-time web search, comprehensive file analysis, and controlled sandboxed code execution.
Unlike traditional single-turn completion APIs, the Responses API introduces a paradigm shift by elevating tools as integral primitives in AI workflows. It enables dynamic plan/execute/observe loops that transform language models into orchestrators of modular, auditable, and resilient agentic tasks. This comprehensive guide dives deep into the API mechanics, practical implementation patterns, and critical operational considerations to help you deploy sophisticated, secure, and cost-effective AI agents.
Ideal readers include: backend engineers architecting AI platforms, AI research leads adopting agentic methodologies, security engineers responsible for sandboxing and secrets management, and product managers designing AI-driven features that demand explainability and safe integration.
Introduction to the Responses API and the 2026 Codex Research Breakthrough
On June 25, 2026, OpenAI unveiled the landmark Codex research paper that advances the frontier of model grounding, hierarchical planning, and secure tooling integration for language AI. Core to this innovation is the Responses API—a developer-centric abstraction that operationalizes these advances with explicit tool schemas, structured multi-step plans, and a robust lifecycle for orchestrated AI workflows.
The paper compellingly demonstrates how decomposing complex tasks into verifiable, incremental steps significantly improves accuracy and interpretability compared to monolithic, single-turn completions. The API mirrors this design, enabling developers to build transparent agentic systems that leverage external knowledge sources, sandboxed computations, and persistent state with fine-grained observability.
This article thoroughly explores the key distinctions from legacy completion APIs, uncovers the architectural foundation of the Responses API, and offers a hands-on, end-to-end example: a multi-step agent that combines real-time web querying, file content extraction, and secure code execution to deliver rich, sourced insights. Finally, it covers essential security practices and operational guidelines to deploy these agents safely at scale.
Section 1: From Traditional Chat Completions to Agentic Response Workflows – Why the Responses API Matters
Historically, OpenAI’s Chat Completion APIs returned free-form text blobs, leaving developers to interpret responses and orchestrate subsequent actions. While effective for straightforward tasks such as Q&A, summarization, or translation, this approach falls short for complex scenarios requiring external evidence integration, deterministic execution, or maintenance of state across steps.
The Responses API introduces a fundamental architectural pivot: it treats AI models as planners and orchestrators interacting with explicitly defined tools via structured schemas. Instead of opaque text, models generate validated tool invocation plans that the application executes within secure, auditable runtimes.
Core Paradigm Shifts Transforming AI Development
- Orchestration over prediction: The model functions as a decision-maker scheduling discrete, verifiable tasks rather than just producing textual completions.
- First-class tool abstractions: Tools—including web search, file readers, code runners, calculators, and databases—are declared with strict, machine-enforced schemas and permissions.
- Multi-step, interactive loops: The agent iteratively plans, executes tool calls, observes outcomes, and refines its plan until reaching a stopping condition.
- Separation of concerns and layered architecture: Human intent specification, model planning, tool execution, and security are distinctly decoupled layers, enabling improved testing, debugging, and policy enforcement.
By embracing these shifts, teams move from managing isolated conversation logs to engineering fully stateful, auditable agents. The Responses API empowers developers to implement an explicit lifecycle of Plan » Execute » Observe » Iterate/Finalize, supported with typed message formats, embedded tool definitions, and structured results that reveal the model’s rationale and decision path.
Implications for Development Teams and Tooling Ecosystems
The modular and declarative nature of the Responses API fosters enhanced collaboration across multiple disciplines:
- Product owners craft coherent user experiences and define measurable success criteria.
- Machine learning engineers engineer prompts, constraints, and tool schemas to guide agent behavior.
- Backend developers build sandboxed tool endpoints with strict input validation and robust execution monitoring.
- Security specialists implement rigorous sandbox policies, secrets management, and access controls.
- Observability teams collect rich trace data for root-cause analysis and compliance audits.
This division of responsibilities encourages reproducible, secure AI workflows that scale effectively in complex enterprise environments.
Section 2: Detailed Anatomy of the Responses API – Schemas, Multi-Step Execution, and Lifecycle Management
The Responses API introduces a concise yet powerful set of core primitives that collectively unlock agentic multi-step capabilities. Mastering these concepts is key to building reliable and maintainable AI agents.
Essential Components and Foundational Concepts
- Declarative prompt/instruction: High-level user intents augmented with metadata such as privacy constraints, style guidelines, or computational budgets.
- Tool definitions: Typed JSON/YAML schemas that outline each tool’s name, input/output specifications, permission requirements, cost estimations, and usage examples. These are registered within the API call context.
- Structured response objects: The API returns composite objects featuring final answers, detailed plans enumerating tool calls with parameters and justifications, or both. These artifacts promote transparency and traceability.
- Iterative execution loop: The canonical loop cycles through: model proposes a plan -> application executes the designated tool calls -> outputs produce observations -> model ingests observations and refines or concludes the plan.
- Step identifiers and checkpointing: Plans and tool calls receive unique IDs enabling durable state persistence, auditing, and rollback capabilities.
Example Tool Definition Schema (Conceptual Overview)
{
"name": "web_search",
"description": "Perform targeted web searches returning top hits with URLs and summaries.",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string"},
"top_k": {"type": "integer", "default": 5}
},
"required": ["query"]
},
"output_schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"type": "object",
"properties": {
"title": {"type": "string"},
"snippet": {"type": "string"},
"url": {"type": "string"}
},
"required": ["title", "url"]
}
}
}
},
"permissions": ["web:search"],
"cost_estimate": {"rpc_ms": 200}
}
This strict schema enforcement ensures malformed or malicious tool invocations are rejected before execution, dramatically enhancing security and system robustness.
The Responses API Multi-Step Tool Call Lifecycle Explained
- Plan Generation: The model emits a plan object detailing the tool invocations (potentially multiple steps executed sequentially or concurrently), including validated inputs and contextual rationales.
- Pre-execution Validation: The platform checks inputs against schemas, enforces permission constraints and resource budgets, and executes static safety scans (e.g., token redaction, sandbox routing).
- Tool Execution: Backend components run tools, either synchronously or asynchronously, with all invocations tagged by unique step IDs and augmented with metadata such as start times, environment details, and resource limits.
- Observation Capture: Post-execution, an observation object containing outputs, execution status, and resource statistics is constructed and fed back into the Responses API context.
- Model Update & Iteration: The model ingests observations and either refines the plan or signals final output, continuing the loop until an explicit completion or a configured step ceiling is met.
- Commit & Cleanup: On finalization, platforms flush logs, rotate ephemeral secrets, and trigger retention policies for transient data generated during execution.
Comprehensive instrumentation at each stage enables deterministic replays, fault-tolerant recovery, and forensic audits.
Robust Observability & Auditing Strategies
- Capture the exact prompt and model state responsible for plan generation.
- Log validated tool invocation payloads and pre-execution inputs.
- Record detailed execution logs, resource usage metrics, and exit statuses for sandboxed tooling.
- Store all observations and subsequent model responses for reproducibility and analysis.
These observability artifacts empower security, compliance, and reliability teams to replay AI decisions, verify adherence to policies, and optimize overall system performance.
Section 3: Step-by-Step Guide to Building a Multi-Step AI Agent Using the Responses API
This section walks through the creation of a multi-faceted agent capable of:
- Accepting a natural language question from the user
- Executing scoped web search queries for relevant evidence
- Optionally analyzing user-uploaded files for deeper context
- Running secure sandboxed code snippets to perform computations or data transformations
- Returning a comprehensive, structured answer enriched with source provenance
Architectural Overview
- Request Handler/Frontend: Validates inputs, attaches files securely, enforces user quotas.
- Tool Registry: Manages tool metadata, interface schemas, permissions, and cost estimates.
- Execution Orchestrator: Implements the Responses API lifecycle, input validation, and progress tracking.
- Tool Implementations: Connectors to external systems – web search engines, file processing services, and sandboxed runtime environments.
- Observability Store: Durable repository for plans, tool calls, outputs, and audit artifacts supporting replay and compliance.
Practical JSON Tool Definitions
{
"web_search": {
"name": "web_search",
"description": "Perform a scoped web search and return top-ranked results with URLs and timestamps.",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string"},
"top_k": {"type": "integer", "default": 5},
"domain_whitelist": {
"type": "array",
"items": {"type": "string"},
"description": "Optional allowed domains filter."
}
},
"required": ["query"]
},
"output_schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"type": "object",
"properties": {
"title": {"type": "string"},
"snippet": {"type": "string"},
"url": {"type": "string"},
"source": {"type": "string"},
"published_at": {"type": "string", "format": "date-time"}
},
"required": ["title", "url"]
}
}
}
},
"permissions": ["web:search"],
"cost_estimate": {"rpc_ms": 150}
},
"file_reader": {
"name": "file_reader",
"description": "Read, extract, and summarize uploaded documents supporting PDF, TXT, and DOCX formats with enforced redaction.",
"input_schema": {
"type": "object",
"properties": {
"file_id": {"type": "string"},
"pages": {"type": "array", "items": {"type": "integer"}, "description": "Optional page selectors."}
},
"required": ["file_id"]
},
"output_schema": {
"type": "object",
"properties": {
"text": {"type": "string"},
"summary": {"type": "string"},
"metadata": {"type": "object"}
},
"required": ["text"]
},
"permissions": ["files:read"],
"cost_estimate": {"rpc_ms": 300}
},
"sandbox_run": {
"name": "sandbox_run",
"description": "Execute code within ephemeral, secure containers supporting Python, Node.js, and Bash with resource limits and output capture.",
"input_schema": {
"type": "object",
"properties": {
"language": {"type": "string", "enum": ["python", "node", "bash"]},
"code": {"type": "string"},
"timeout_seconds": {"type": "integer", "default": 5},
"memory_mb": {"type": "integer", "default": 256}
},
"required": ["language", "code"]
},
"output_schema": {
"type": "object",
"properties": {
"stdout": {"type": "string"},
"stderr": {"type": "string"},
"exit_code": {"type": "integer"},
"runtime_ms": {"type": "integer"}
},
"required": ["stdout", "exit_code"]
},
"permissions": ["sandbox:execute"],
"cost_estimate": {"rpc_ms": 2000}
}
}
End-to-End Orchestration Flow (Pseudocode)
function handleUserRequest(userRequest):
user = authenticate(userRequest.token)
validateQuota(user)
context = buildContext(userRequest) // includes prompt, attached files metadata, session info
response = ResponsesAPI.create({
model: "codex-structured-2026",
instructions: context.prompt,
tools: [web_search_tool, file_reader_tool, sandbox_run_tool],
constraints: { max_steps: 8, cost_limit_ms: 10000 }
})
while not response.is_final and response.step_count < constraints.max_steps:
if response.intent == "call_tool":
plan = response.plan
for tool_call in plan.tool_calls:
validateToolInput(tool_call)
checkPermissions(user, tool_call)
observation = executeTool(tool_call)
auditLog.append({ "step_id": tool_call.step_id, "observation": observation })
response = ResponsesAPI.update({
conversation_id: response.conversation_id,
observation: observation,
step_id: tool_call.step_id
})
if response.is_final:
break
else if response.intent == "final_answer":
break
else:
response = ResponsesAPI.update({ conversation_id: response.conversation_id, observation: {"note":"no-op"} })
final_answer = response.output
redactAndReturn(user, final_answer)
Pragmatic Python Implementation Skeleton
import json
import uuid
# Hypothetical SDK imports—replace with your actual client libraries
from responses_api import ResponsesClient
from tools import WebSearchClient, FileServiceClient, SandboxRunner
from validator import validate_schema, check_permissions
RESPONSES_MODEL = "codex-structured-2026"
MAX_STEPS = 8
responses = ResponsesClient(api_key="YOUR_API_KEY")
web_search = WebSearchClient(api_key="YOUR_WEB_SEARCH_API_KEY")
file_service = FileServiceClient(storage_endpoint="YOUR_STORAGE_ENDPOINT")
sandbox = SandboxRunner(endpoint="YOUR_SANDBOX_ENDPOINT")
def handle_request(user_token: str, prompt: str, file_ids=None):
user = authenticate(user_token)
session_id = str(uuid.uuid4())
tools = [web_search.tool_def(), file_service.tool_def(), sandbox.tool_def()]
r = responses.create({
"model": RESPONSES_MODEL,
"instructions": prompt,
"tools": tools,
"metadata": {"session_id": session_id, "user_id": user.id},
"constraints": {"max_steps": MAX_STEPS}
})
step = 0
checkpoints = []
while not r.final and step < MAX_STEPS:
step += 1
plan = r.plan
if not plan or "tool_calls" not in plan:
r = responses.update(r.session_id, observation={"note": "Please provide an executable tool plan."})
continue
for call_index, tool_call in enumerate(plan["tool_calls"]):
if call_index > 0:
if step >= MAX_STEPS:
break
step += 1
tool_name = tool_call.get("name")
arguments = tool_call.get("arguments", {})
validate_schema(tool_name, arguments)
check_permissions(user, tool_call)
try:
if tool_name == "web_search":
result = web_search.execute(arguments)
elif tool_name == "file_reader":
result = file_service.read(arguments)
elif tool_name == "sandbox_run":
result = sandbox.run(arguments)
else:
result = {
"error": "unsupported_tool",
"tool_name": tool_name
}
except Exception as exc:
result = {
"error": "tool_execution_failed",
"tool_name": tool_name,
"detail": str(exc)
}
observation = {
"tool_call_id": tool_call.get("id"),
"tool_name": tool_name,
"result": result
}
checkpoints.append({
"step": step,
"observation": observation
})
r = responses.update(
r.session_id,
observation=observation,
step_id=tool_call.get("step_id")
)
if r.final or r.plan != plan:
break
if r.final:
return r.output
return "The request stopped after reaching the configured step limit."
class AuthenticatedUser:
def __init__(self, stable_user_id: str):
self.id = stable_user_id
def authenticate(user_token: str):
# Placeholder only: replace with real token verification that returns a stable user ID.
if not user_token:
raise PermissionError("A user token is required.")
return AuthenticatedUser(str(uuid.uuid5(uuid.NAMESPACE_OID, user_token)))
This skeleton uses placeholder client classes, response fields, validation helpers, authentication logic, and simplified step accounting. Treat it as an architectural sketch, and use the official SDK example below as the reference for response inspection and loop termination.
Mapping the Skeleton to the Official Responses API
This example swaps the custom connectors for hosted web search, hosted file search, and one narrow local calculation function.
The Responses API supports built-in web search, built-in file search, and application-defined function tools. For a function tool, the model returns the function name, JSON-encoded arguments, and a call identifier. Your application validates the request, runs the local function, and submits its result as a matching function-call output. The current fields are documented in the Responses API reference and the function-calling guide.
Before running this example, set OPENAI_MODEL to a model enabled for your API project and OPENAI_VECTOR_STORE_ID to an accessible vector store. Create the vector store and add the files you want the agent to search by following the file-search guide.
import hashlib
import json
import os
from typing import Any
from openai import OpenAI
MODEL = os.environ["OPENAI_MODEL"]
VECTOR_STORE_ID = os.environ["OPENAI_VECTOR_STORE_ID"]
MAX_ROUNDS = 6
client = OpenAI()
tools = [
{"type": "web_search"},
{
"type": "file_search",
"vector_store_ids": [VECTOR_STORE_ID],
},
{
"type": "function",
"name": "calculate_summary",
"description": "Calculate the sum or arithmetic mean of a list of numbers.",
"parameters": {
"type": "object",
"properties": {
"operation": {
"type": "string",
"enum": ["sum", "mean"],
},
"values": {
"type": "array",
"items": {"type": "number"},
"minItems": 1,
},
},
"required": ["operation", "values"],
"additionalProperties": False,
},
"strict": True,
},
]
def calculate_summary(arguments: dict[str, Any]) -> dict[str, float]:
if set(arguments) != {"operation", "values"}:
raise ValueError("Unexpected or missing arguments")
operation = arguments["operation"]
values = arguments["values"]
if operation not in {"sum", "mean"}:
raise ValueError("Unsupported operation")
if not isinstance(values, list) or not values:
raise ValueError("values must be a non-empty list")
if any(
isinstance(value, bool) or not isinstance(value, (int, float))
for value in values
):
raise ValueError("Every value must be a number")
total = float(sum(values))
result = total if operation == "sum" else total / len(values)
return {"result": result}
def run_agent(question: str, app_user_id: str) -> str:
if not question.strip():
raise ValueError("A question is required")
if not app_user_id:
raise ValueError("A stable internal user ID is required")
safety_identifier = hashlib.sha256(
app_user_id.encode("utf-8")
).hexdigest()
response = client.responses.create(
model=MODEL,
input=(
"Use the indexed files and current web sources when relevant. "
"Use calculate_summary when arithmetic is needed.\n\n"
f"User question: {question}"
),
tools=tools,
max_tool_calls=4,
metadata={"workflow": "research_agent"},
safety_identifier=safety_identifier,
)
rounds = 0
while True:
function_calls = [
item for item in response.output
if item.type == "function_call"
]
if not function_calls:
return response.output_text
if rounds >= MAX_ROUNDS:
raise RuntimeError("The agent exceeded its local round limit")
rounds += 1
function_outputs = []
for item in function_calls:
try:
arguments = json.loads(item.arguments)
if item.name != "calculate_summary":
raise ValueError(f"Unsupported function: {item.name}")
result = calculate_summary(arguments)
output = {"ok": True, "result": result}
except (json.JSONDecodeError, TypeError, ValueError) as exc:
output = {"ok": False, "error": str(exc)}
function_outputs.append({
"type": "function_call_output",
"call_id": item.call_id,
"output": json.dumps(output),
})
response = client.responses.create(
model=MODEL,
previous_response_id=response.id,
input=function_outputs,
tools=tools,
max_tool_calls=4,
metadata={"workflow": "research_agent"},
safety_identifier=safety_identifier,
)
if __name__ == "__main__":
user_question = input("Question: ")
internal_user_id = input("Stable internal user ID: ")
print(run_agent(user_question, internal_user_id))
The run_agent function receives the user’s actual question and a stable internal user ID. It hashes that identifier before sending it as the safety identifier, rather than transmitting the underlying account value.
The user’s question is included in the first request rather than hidden in a generic instruction. Web search and file search are hosted tools, so the API processes those calls within a response. The local loop only needs to dispatch application-defined function calls.
File search works against the vector store identified by VECTOR_STORE_ID. Populate that store with the documents the application is authorized to expose, and keep separate stores or filtering rules where users have different access rights. A model’s ability to retrieve a file does not replace the application’s responsibility to enforce document access.
Inspect every output item instead of assuming that a fixed array position contains the next function request. Output length and order can vary. Where supported by the SDK, output_text provides the aggregated text result; see the Responses API reference.
Each local function request contains JSON-string arguments and a model-generated call_id. The application parses and validates the arguments, permits only a known function, and returns a function_call_output with the same identifier. The follow-up request uses previous_response_id to continue from the earlier response, as described in the conversation-state guide.
The loop checks the newest response before enforcing its local round limit. That detail prevents a valid final response from being discarded after the last permitted follow-up request. The API-level max_tool_calls setting separately limits the total built-in tool calls processed in one response; it does not replace an application-controlled round, time, or cost ceiling.
The function schema enables strict mode and rejects undeclared properties. Structured Outputs enforces the declared schema, but the Structured Outputs guide warns that structured results can still contain mistakes. Keep authorization, resource ownership, numeric ranges, and other business checks in application code.
Where Computer Use Fits
Computer use is covered here at the conceptual level only. The Responses API defines a computer tool for controlling a virtual computer. Its declaration is:
computer_tool = {"type": "computer"}
Your harness executes the requested actions in its target environment and returns observations such as screenshots for the next model turn. Follow the computer-use guide when implementing that interaction loop.
Place computer control behind a separate trust boundary. Use an isolated browser or virtual environment, a dedicated identity, restricted network access, and no ambient credentials. Require human confirmation for purchases, outbound messages, destructive changes, permission grants, and comparable consequential actions. After confirmation, verify the current target and action again instead of relying on an earlier page state.
Section 4: Production Checklist for Multi-Step Agents
- Validate generated arguments. Constrain generation with a strict schema, then validate the parsed object before execution. Reject unknown properties, invalid ranges, oversized inputs, and resource identifiers outside the caller’s scope.
- Allow-list tools per request. Submit only tools permitted for that user and workflow. Repeat authorization when the tool runs; a schema-valid call is not proof of access.
-
Set independent limits. Bound local rounds, elapsed time, payload size, retries, and per-tool work. Use
max_tool_callsto limit built-in calls within a response, not as the sole loop guard. - Make mutations idempotent. Give write operations an application-generated idempotency key. Store completed outcomes so a retry cannot create a duplicate payment, message, ticket, or other side effect.
-
Log each call boundary. Record the response ID,
call_id, tool name, sanitized argument digest, authorization decision, status, and sanitized result. The function-calling guide documents howcall_idassociates a function request with its output. -
Choose retention deliberately. The
storeoption defaults to true, and stored responses are retained for at least 30 days subject to documented exceptions. Review the data controls and retention guide. If you setstore=False, resend the prior input and output items instead of relying onprevious_response_id. -
Use a per-user safety identifier. Hash a stable internal user identifier instead of sending an email address or username. The API accepts a
safety_identifierof up to 128 characters. - Bound failures and retries. Set connector timeouts, retry only transient failures, use jittered backoff, and cap attempts. Return sanitized error observations; do not send secrets, stack traces, or unrestricted upstream responses back to the model.
- Evaluate full traces. Test malformed arguments, denied calls, missing tools, stale files, conflicting web evidence, repeated function requests, and exhausted limits. Re-run those cases whenever prompts, schemas, permissions, models, retrieval data, or tool implementations change.
Conclusion
A maintainable multi-step agent is an orchestration system rather than a single model request. The model can propose actions, but the application owns identity, authorization, validation, execution boundaries, retries, retention, audit records, and stopping conditions. Keeping those controls outside the model makes each action inspectable and gives operators a concrete enforcement point.
Start with a test vector store, one read-only hosted tool, and one narrow local function. Record sanitized traces for successful, denied, malformed, and limit-exhaustion cases before adding computer control or write-capable tools. Keep model and resource identifiers in configuration, and recheck the current API documentation when upgrading the client or changing tool definitions.
