Run Codex with the OpenAI Agents API in a sandbox
Last updated Oct 22, 2026 The OpenAI Agents API ↗︎ runs the Codex agent loop at OpenAI. With a self-hosted environment, Codex runs commands and edits files in your Cloudflare account instead. The Cloudflare template runs the Codex executor of each session in its own Linux sandbox: a Container that one Durable Object starts for that session.

The template ↗︎ contains the Worker and the container image that this guide deploys.
How it works
OpenAI sends signed webhooks to a Worker in your account as a session starts, needs an environment, works, and goes idle. The Worker verifies each webhook, retrieves the session from OpenAI, and passes it to the Durable Object named for that session. The Durable Object starts a container that runs codex exec-server. The executor connects out to OpenAI with a restricted API key. It runs commands from the agent against files in /workspace, which stay in your Cloudflare account.
Prerequisites
You need:
- A Cloudflare account on the Workers Paid plan with access to Containers
- OpenAI Agents API access and an OpenAI API key
curl- For manual deployment, Node.js 24 ↗︎ or later and a running Docker ↗︎ daemon
You also need a restricted OpenAI API key for codex exec-server, called the executor key in this guide. It needs the api.model.read and api.agents.environments.connect permissions. The OpenAI API key that the Worker uses needs api.agents.read. Both keys must belong to the same organization, project, and owner, whether that owner is a user or a service account.
Deploy the template
These steps create an OpenAI agent, deploy the Worker and its container with the Deploy to Cloudflare button, register the webhook, and run a test task in /workspace.
- Set your OpenAI API key, then create an agent:
export OPENAI_API_KEY="<OPENAI_API_KEY>"
curl "https://api.openai.com/v1/agents" \
--request POST \
--header "OpenAI-Beta: agents=v1" \
--header "Authorization: Bearer $OPENAI_API_KEY" \
--json '{
"name": "sandbox-demo",
"model": "gpt-5.6-sol"
}'
Copy the id field from the response and save it as the agent ID:
export OPENAI_AGENT_ID="agent_..."
- Generate a shared secret for the container cleanup endpoint, and save it:
bash
openssl rand -hex 32
Select Deploy to Cloudflare:
Enter these values when prompted:
| Variable | Value |
|---|---|
OPENAI_API_KEY |
The OpenAI key used to retrieve session state |
OPENAI_EXECUTOR_API_KEY |
The restricted executor key |
OPENAI_AGENT_ID |
The agent ID from step 1 |
OPENAI_WEBHOOK_SECRET |
pending-webhook-registration for the first deployment |
EXECUTOR_CLIENT_SECRET |
The shared secret from this step |
Save the deployed Worker URL:
bash
export WORKER_URL="https://<YOUR_WORKER>.workers.dev"
The template starts a container as soon as OpenAI creates a session, and snapshots the container when the session goes idle. An idle session keeps its container for EXECUTOR_KEEP_ALIVE_SECONDS, which wrangler.jsonc in the template sets to 30 seconds.
3. In OpenAI project webhook settings ↗︎, register https://<YOUR_WORKER>.workers.dev/webhook. OpenAI must be able to reach this URL.
Subscribe to these events:
- agent.session.created
- agent.session.action_required
- agent.session.in_progress
- agent.session.idle
- agent.session.failed
Copy the signing secret that OpenAI returns. In Settings > Variables and Secrets for the Worker, replace OPENAI_WEBHOOK_SECRET with it, then select Deploy. If you deployed manually, run this command in the templates/openai-agents-api directory instead:
bash
npx wrangler secret put OPENAI_WEBHOOK_SECRET
Check the setup:
bash
curl --fail-with-body "$WORKER_URL/health"
The Worker is ready when the response contains "configured": true and "webhook_configured": true.
4. Create a self-hosted session:
curl https://api.openai.com/v1/agents/sessions \
--request POST \
--header "OpenAI-Beta: agents=v1" \
--header "Authorization: Bearer $OPENAI_API_KEY" \
--json '{"agent_id": "'"$OPENAI_AGENT_ID"'", "environment": {"type": "self_hosted", "workspace_directory": "/workspace"}}'
Copy the id field from the response and save it as the session ID:
export SESSION_ID="sess_..."
Open the session event stream in one terminal:
curl --no-buffer \
"https://api.openai.com/v1/agents/sessions/$SESSION_ID/events" \
--header "OpenAI-Beta: agents=v1" \
--header "Authorization: Bearer $OPENAI_API_KEY" \
--header "Accept: text/event-stream"
While the stream is open, submit a task from another terminal. Export OPENAI_API_KEY and SESSION_ID in that terminal first:
curl "https://api.openai.com/v1/agents/sessions/$SESSION_ID/events" \
--request POST \
--header "OpenAI-Beta: agents=v1" \
--header "Authorization: Bearer $OPENAI_API_KEY" \
--json '{
"events": [
{
"type": "session.input.message",
"input": [
{
"role": "user",
"content": [
{
"type": "input_text",
"text": "Use the shell to write hello to /workspace/hello.txt, then read it."
}
]
}
]
}
]
}'
The event stream shows the progress of the agent and its reply.
<details>
<summary>
Deploy manually
</summary>
To deploy from your terminal instead of with the button in step 2:
- Clone the template repository, install its dependencies, and log in to Cloudflare:
bash
git clone https://github.com/cloudflare/sandbox-sdk.git
cd sandbox-sdk/templates/openai-agents-api
npm install
npx wrangler login
- Generate a shared secret for the container cleanup endpoint, and save it:
bash
openssl rand -hex 32
- Store the Worker secrets. Enter your OpenAI API key, the executor key, the agent ID, and the shared secret when prompted:
bash
npx wrangler secret put OPENAI_API_KEY
npx wrangler secret put OPENAI_EXECUTOR_API_KEY
npx wrangler secret put OPENAI_AGENT_ID
npx wrangler secret put EXECUTOR_CLIENT_SECRET
- Deploy the Worker and its container:
bash
npm run deploy
To change the keep-alive, prewarm, and snapshot settings, edit <code>EXECUTOR_KEEP_ALIVE_SECONDS</code>, <code>EXECUTOR_PREWARM_ENABLED</code>, and <code>EXECUTOR_SNAPSHOTS_ENABLED</code> in <code>wrangler.jsonc</code>, then run <code>npm run deploy</code> again.
Save the deployed Worker URL, then continue with step 3.
</details>
<details>
<summary>
Reconnect an existing session
</summary>
Open the session event stream again, then send more input:
curl "https://api.openai.com/v1/agents/sessions/$SESSION_ID/events" \
--request POST \
--header "OpenAI-Beta: agents=v1" \
--header "Authorization: Bearer $OPENAI_API_KEY" \
--json '{
"events": [
{
"type": "session.input.message",
"input": [
{
"role": "user",
"content": [
{
"type": "input_text",
"text": "Read /workspace/hello.txt again."
}
]
}
]
}
]
}'
</details>
Build an application on the Agents API
The basic example ↗︎ in the template is a TypeScript application that uses the OpenAI Agents API TypeScript SDK. It creates self-hosted sessions that run on the executor Worker. Its HTTP endpoints send the first input, send follow-up input, and clean up. The POST /demo endpoint runs the whole workflow: it creates a session, writes and reads a file in the container, sends a follow-up message, and then deletes the OpenAI session and its executor.
Clean up
Delete the OpenAI session:
curl "https://api.openai.com/v1/agents/sessions/$SESSION_ID" \
--request DELETE \
--header "OpenAI-Beta: agents=v1" \
--header "Authorization: Bearer $OPENAI_API_KEY"
To stop the container of a session at once, call the cleanup endpoint with the shared secret from deployment:
export WORKER_URL="https://<YOUR_WORKER>.workers.dev"
export EXECUTOR_CLIENT_SECRET="<EXECUTOR_CLIENT_SECRET>"
curl --fail-with-body \
--request DELETE \
--header "Authorization: Bearer $EXECUTOR_CLIENT_SECRET" \
"$WORKER_URL/executors/$SESSION_ID"
Deleting an OpenAI session does not send a webhook to the Worker. The Worker stops a container and deletes its record of the container snapshot when it receives an agent.session.failed webhook, when a session lookup returns 404 Not Found, or when you call the cleanup endpoint. An idle session that still exists keeps its snapshot for its next environment connection.
Session lifecycle
- The application creates or retrieves an OpenAI session and sends input through the Agents API.
- When
EXECUTOR_PREWARM_ENABLEDistrue, as in the template, anagent.session.createdwebhook makes the Worker retrieve the session and start its container with the environment ID and remote URL of the session. - An
agent.session.action_requiredwebhook makes the Worker retrieve the session, confirm that the configured agent owns it, and read the environment ID and remote URL that the session needs. - The Durable Object named for the session starts a container with those connection details and the executor key.
codex exec-serverconnects out to OpenAI. - Each container start, environment connection, and
agent.session.in_progresswebhook resets a deadline ofEXECUTOR_KEEP_ALIVE_SECONDS. When the deadline passes, the Worker retrieves the session. If the session is still active, the Worker sets a new deadline. - An
agent.session.idlewebhook makes the Worker snapshot the container and reset the deadline. When the deadline passes, the Worker stops the container and keeps the snapshot. - New input sends another
agent.session.action_requiredwebhook. The Worker reuses a running container for the same environment ID, or starts the next environment from the saved snapshot.

Keep the workspace between turns
Public beta
Container snapshots are in public beta. Features and behavior may change.
When a session goes idle, the Worker snapshots the container filesystem before it stops the container. The next environment connection starts from that snapshot, so the files in /workspace come back. Running processes and memory do not. If the snapshot fails, the Worker leaves the container running and schedules another lifecycle check. For more information, refer to Sandbox lifetime.
A session uses its snapshot only while the session lasts. The Worker deletes the snapshot record of a session when the session fails, when a later lookup finds that the session no longer exists, or when you call the cleanup endpoint. The next session then starts from the image. The Worker does not delete the snapshot itself.
With EXECUTOR_SNAPSHOTS_ENABLED set to false, each new executor starts with an empty /workspace. To keep files after a session ends, store them somewhere else, such as an R2 bucket. For more information, refer to Mount an R2 bucket.
Add tools to the container
The templates/openai-agents-api/Dockerfile file in the template defines the executor image. Add Debian packages to its apt-get install command. For example, to add jq and Python:
RUN apt-get update \
&& apt-get install --yes --no-install-recommends \
ca-certificates \
curl \
git \
jq \
python3 \
ripgrep \
&& rm -rf /var/lib/apt/lists/*
You can also install language tools in the image, such as global npm packages. Do not put API keys or other secrets in the Dockerfile. Pass them at runtime through Worker secrets and container environment variables.
To build and deploy the new image, run npm run deploy in the templates/openai-agents-api directory. New containers start from it. A session that starts from a snapshot keeps the filesystem of its old image, so it does not get the new packages.
Security considerations
The template is a minimal example. Review these defaults before you adapt it for production:
- Your OpenAI API key, the webhook secret, and
EXECUTOR_CLIENT_SECRETstay in Worker secrets. The executor key goes into the container asCODEX_API_KEY, where every process in the container can read it. Give the executor key only the two permissions it needs. - The container has Internet access so that
codex exec-servercan reach OpenAI. Every command the agent runs has the same access. To restrict destinations or add credentials to requests from your Worker, refer to Outbound traffic. - OpenAI must reach
/webhookwithout an interactive Access login. The Worker checks the signature on each webhook, and the cleanup endpoint requiresEXECUTOR_CLIENT_SECRET. If you protect other routes with Cloudflare Access, use path-specific policies that leave/webhookreachable.
For more information about what a sandbox exposes to the code inside it, refer to Sandbox security.
Related resources
- OpenAI Agents API template ↗︎
- OpenAI Agents API documentation ↗︎
- OpenAI Python Cloudflare webhook example ↗︎
- OpenAI TypeScript Cloudflare webhook example ↗︎
- Cloudflare Containers
Was this helpful?
YesNo
On this page
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/sandbox/coding-agents/openai-agents-api/#page","headline":"Run Codex with the OpenAI Agents API in a sandbox","description":"Deploy a self-hosted OpenAI Agents API environment that runs each Codex session in its own Linux sandbox on Containers.","url":"https://developers.cloudflare.com/sandbox/coding-agents/openai-agents-api/","inLanguage":"en","image":"https://developers.cloudflare.com/sandbox/coding-agents/openai-agents-api/og.png?v=ade466bed145a5e4","dateModified":"2026-10-22","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
description: Run the OpenCode coding agent on a GitHub repository in a Linux sandbox and read its changes as a diff. title: Run OpenCode in a sandbox image: https://developers.cloudflare.com/sandbox/coding-agents/opencode/og.png?v=3b97c1f136b8dfe8
Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/sandbox/llms.txt
Use this file to discover all available pages before exploring further.
