
100% private · no tracking · works offline100% client-side/no data leaves your browser/no accounts/works offline
A production guide to Programmatic Tool Calling in the OpenAI Responses API, including tool eligibility, structured contracts, nested-call continuation, routing boundaries, and operational safeguards.

Free toolkit
85+ private dev tools
Everything runs in your browser. Zero tracking, no sign-up.
Browse toolsOpenAI Programmatic Tool Calling lets a model write JavaScript that coordinates approved tools inside a Responses API request. It is useful when code can fetch, filter, join, validate, or aggregate several tool results before the model sees a smaller final result. It does not remove your tool executor, authorization rules, approval flow, or responsibility for precise contracts.
In this guide, you'll learn:
OpenAI introduced Programmatic Tool Calling with GPT‑5.6 on July 9, 2026. The model can produce a program output item containing JavaScript that runs in a fresh isolated V8 runtime. That program may call eligible tools, use top-level await, branch, loop, run independent calls concurrently, and emit a reduced result with text(...) or image(...).
The hosted runtime is intentionally narrow. According to the official Programmatic Tool Calling guide, it has no Node.js environment, package installation, direct network access, general filesystem, subprocesses, console, or persistent JavaScript state between programs. Enabled tools are its only route to external systems.
That changes who writes orchestration, not who owns external execution:
| Responsibility | Direct function calling | Programmatic Tool Calling |
|---|---|---|
| Decide which tool to call | Model, one decision at a time | Model-generated program for a bounded stage |
| Loops, filters, joins | Usually application or repeated model turns | Generated JavaScript |
| Execute client functions | Your application | Your application |
| Authorize the operation | Your application | Your application |
| Resume after a client call | Application sends tool output | Application sends output with call_id and caller |
| Final semantic judgment | Model | Model after the program stage |
Some early articles describe the feature as replacing the client tool loop. That is too broad. A generated program can pause whenever it calls a client-owned function. Your application still executes those calls and returns results until the service produces a final assistant message. The gain is that predictable control flow and intermediate reduction live inside the program rather than requiring fresh model judgment after every result.
This makes tool boundaries more important, not less. The same principle behind an AI coding-agent permission contract applies here: orchestration instructions guide behavior, but actual safety comes from the capabilities and permissions enforced around each tool.
Programmatic Tool Calling fits a bounded stage with predictable data flow. Good examples include:
Use direct calls when the next action requires fresh model judgment, approval, a native artifact, or source citation validation.
| Task shape | Recommended route |
|---|---|
| One lookup or action | Direct |
| Many structured reads followed by filtering | Programmatic |
| Adaptive investigation where each result changes the search | Direct |
| Parallel calls with predictable aggregation | Programmatic |
| Send, delete, purchase, deploy, or update | Direct with approval by default |
| Final check of citations or generated files | Direct unless native evidence is preserved and validated |
Parallelism alone is not a reason to enable it. If two small calls already fit one ordinary tool turn, adding generated code increases complexity without meaningful reduction.
The distinction also separates Programmatic Tool Calling from the GPT‑5.6 multi-agent beta. A program coordinates tools with code; multi-agent divides independent reasoning work among subagents. Use neither until the task shape justifies it.
Enable the hosted tool and opt individual tools into programmatic access with allowed_callers. Add output_schema when a function returns predictable structured JSON; the generated JavaScript needs a reliable return contract.
const tools = [
{
type: "function",
name: "get_inventory",
description:
"Return an object with sku (string) and available_units (number).",
parameters: {
type: "object",
properties: {
sku: { type: "string", minLength: 1 },
},
required: ["sku"],
additionalProperties: false,
},
output_schema: {
type: "object",
properties: {
sku: { type: "string" },
available_units: { type: "number" },
},
required: ["sku", "available_units"],
additionalProperties: false,
},
allowed_callers: ["programmatic"],
},
{
type: "function",
name: "get_demand",
description:
"Return an object with sku (string) and requested_units (number).",
parameters: {
type: "object",
properties: {
sku: { type: "string", minLength: 1 },
},
required: ["sku"],
additionalProperties: false,
},
output_schema: {
type: "object",
properties: {
sku: { type: "string" },
requested_units: { type: "number" },
},
required: ["sku", "requested_units"],
additionalProperties: false,
},
allowed_callers: ["programmatic"],
},
{ type: "programmatic_tool_calling" },
];
The allowed_callers values define the route:
["direct"]: direct model call only;["programmatic"]: generated program only;["direct", "programmatic"]: either route.Do not give every function both routes “for flexibility.” That creates ambiguous orchestration and makes duplicate work harder to detect. Assign a tool to the smallest route its job requires.
Supported program-callable types currently include function, custom, MCP, apply_patch, local and hosted shell, and code_interpreter. Tool Search remains top-level: an already-running program cannot discover a deferred tool. The model must load that tool before starting the program that uses it.

A correct client must preserve every response item, execute returned functions, copy each call's caller, and continue until it receives a final message item. The caller field connects a nested function result to the program that is waiting for it.
import OpenAI from "openai";
const openai = new OpenAI();
const implementations = {
get_inventory: async ({ sku }) => ({
sku,
available_units: await inventoryRepository.availableUnits(sku),
}),
get_demand: async ({ sku }) => ({
sku,
requested_units: await demandRepository.requestedUnits(sku),
}),
};
const input = [
{
role: "user",
content: "Compare inventory with demand for sku_123.",
},
];
for (let turn = 0; turn < 8; turn += 1) {
const response = await openai.responses.create({
model: "gpt-5.6",
store: false,
input,
tools,
});
if (response.status !== "completed") {
throw new Error(`Response ended with status ${response.status}`);
}
// Preserve program, reasoning, program_output, and message items.
input.push(...response.output);
const calls = response.output.filter(
(item) => item.type === "function_call",
);
if (calls.length > 0) {
const outputs = await Promise.all(
calls.map(async (call) => {
const run = implementations[call.name];
if (!run) throw new Error(`Unknown tool: ${call.name}`);
const result = await run(JSON.parse(call.arguments));
return {
type: "function_call_output",
call_id: call.call_id,
caller: call.caller,
output: JSON.stringify(result),
};
}),
);
input.push(...outputs);
continue;
}
const message = response.output.find((item) => item.type === "message");
if (message) {
console.log(response.output_text);
break;
}
if (turn === 7) {
throw new Error("Programmatic tool loop exceeded the turn limit");
}
}
The explicit turn limit is an application guard, not an OpenAI requirement. Production code should also validate function arguments, isolate each call's error, apply timeouts, and distinguish retryable transport failures from permanent domain failures.
With stored responses, continuation can use previous_response_id. With store: false, replay the response output items as shown. store: false does not itself enable Zero Data Retention; that is an organization or project setting.
Generated code is strongest for read-heavy reduction. Keep side effects direct unless a carefully designed approval policy says otherwise.
For example:
Do not expose create_purchase_order to the program merely because it is convenient. A loop bug, duplicated result, or ambiguous stop condition can multiply a write. If programmatic writes are unavoidable, require server-side idempotency keys, per-request limits, authorization on every call, and approval where the MCP or tool policy supports it.
That separation is consistent with the safe-output model described in GitHub agentic workflow security: untrusted or model-generated work can prepare a proposed action, while a controlled boundary decides whether the action reaches the real system.
A program cannot ask what your undocumented response means. Treat every tool as an API consumed by generated code.
price: 20 is ambiguous. Prefer price_minor_units: 2000 plus currency: "INR", or document that price is a decimal in a named currency. Decide whether missing data is null, an empty array, or an error. Do not alternate.
Avoid a success-shaped string such as "SKU not found". Return a predictable domain result or throw an error your executor maps into a documented failure. The generated program needs a stable branch condition.
{
"ok": false,
"error": {
"code": "SKU_NOT_FOUND",
"retryable": false
}
}
Make pagination, maximum rows, and truncation explicit. A program that requests thousands of full records merely moves the context and latency problem into tool infrastructure. Return only fields needed by the bounded stage.
Use additionalProperties: false, enums, formats, and narrow numeric ranges where the domain allows. Loose schemas force generated code to guess.
Programmatic execution can turn one model decision into a burst of tool traffic. Protect downstream systems even when the generated program behaves reasonably.
Prompt routing should state the bounded stage, eligible tools, output schema, evidence fields, concurrency, retry maximum, and stop condition. OpenAI's current GPT‑5.6 prompting guidance explicitly recommends task-specific routing rather than “use Programmatic Tool Calling efficiently.”
Use Programmatic Tool Calling only to read inventory and demand.
Run at most 10 calls with concurrency 4. Retry a transient failure once.
Return one JSON array containing sku, available_units, requested_units,
and shortage_units. Do not call any write tool. Stop when every requested
SKU has either a complete record or a structured error.
Do not assume cost always falls. Fewer model handoffs can reduce repeated processing, but programs may call more tools or retrieve more data than a direct workflow. Compare successful-task rate, model tokens, tool-infrastructure cost, end-to-end latency, and failure-recovery cost.
Log the workflow as a causal graph rather than a flat list:
call_id and opaque fingerprint;call_id and parent caller.caller_id;Do not log raw secrets, access tokens, personal records, or full model-generated code by default. Store a hash or tightly access-controlled sample when debugging requires it. The fingerprint is opaque replay state, not a business identifier.
Tracing also exposes duplicate work: the same record fetched programmatically and then fetched again directly, a retry after success, or a continuation turn that did not preserve prior output items.
The broader lesson matches the site's AI code-review workflow: automation becomes dependable when the review and evidence path is designed alongside execution, not bolted on after incidents.
The isolated V8 runtime cannot directly access the network or filesystem, but that does not make every program safe. It can reach whatever your enabled tools can reach. Enforce authentication and authorization inside the tool implementation; never trust an account ID supplied by generated arguments.
Programmatic Tool Calling can participate in Zero Data Retention workflows without a persistent code container. However, ZDR must already be enabled, and eligibility depends on the complete request—including model, tools, and third-party services. store: false provides stateless continuation behavior; it is not a ZDR switch.
For MCP tools, require_approval can pause the program. Use it for operations that cross a meaningful trust boundary. If approval cannot be represented clearly, keep the tool direct.
Start with deterministic fixtures and a read-only surface.
| Test | Expected result |
|---|---|
| Two independent reads | Concurrent calls, one reduced output |
| One permanent domain error | Structured failure, no retry |
| One transient timeout | At most the configured retries |
| Unknown tool name | Executor rejects it immediately |
Missing caller in output |
Test fails before API continuation |
| Duplicate function call | Idempotent read or deduplicated execution |
| Oversized tool result | Truncated or rejected by contract |
| No final message after limit | Workflow stops and reports failure |
| Attempted write in read stage | Tool unavailable or authorization denied |
Compare the PTC workflow with a direct-call baseline on representative tasks. Measure correctness first. A faster orchestration that silently drops evidence or mishandles partial failures is not an improvement.
It is a Responses API capability where a supported model writes JavaScript that coordinates eligible tools inside an isolated hosted V8 runtime. The program can branch, loop, call tools concurrently, and reduce intermediate results before returning control to the model.
OpenAI executes the generated orchestration program, but your application still executes client-owned function calls. It must return each result as function_call_output with the original call_id and caller so the correct program resumes.
Not directly. The runtime has no direct network access, Node.js, general filesystem, packages, console, or subprocess execution. It can affect external systems only through the tools enabled in the request.
Usually not. OpenAI recommends direct calling by default for writes and approval-sensitive actions. Keep aggregation programmatic, then hand a proposed action to a direct, authorized, and reviewable tool boundary.
store: false the same as Zero Data Retention?#No. store: false supports stateless continuation, but ZDR must be enabled for the organization or project. Retention eligibility depends on the full request, including tools and third-party services.
Programmatic Tool Calling is valuable when predictable code can compress a tool-heavy stage into a smaller structured result. Its production value comes from strict schemas, bounded routing, caller-preserving continuation, read/write separation, and observable failure limits—not from handing generated code a broad tool surface. Start with one read-only aggregation, compare it against a direct baseline, and expand only when the evidence shows a real reliability or latency gain.
Comments
Sign in to join the discussion.
No comments yet. Be the first to share your thoughts.