---
description: Deploy a self-hosted OpenAI Agents API environment that runs each Codex session in its own Linux sandbox on Containers.
title: Run Codex with the OpenAI Agents API in a sandbox
image: https://developers.cloudflare.com/sandbox/coding-agents/openai-agents-api/og.png?v=ade466bed145a5e4
---

[Skip to content](#main-content)

> 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.

# Run Codex with the OpenAI Agents API in a sandbox

Last updated Sep 30, 2026|Copy as Markdown| [View as Markdown](https://developers.cloudflare.com/sandbox/coding-agents/openai-agents-api/index.md)| [Agent setup](https://developers.cloudflare.com/agent-setup/)

The [OpenAI Agents API ↗︎](https://developers.openai.com/api/docs/guides/agents-api/overview) 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](https://developers.cloudflare.com/containers/) that one Durable Object starts for that session.

![Architecture showing an application creating an OpenAI task, webhooks starting a Cloudflare container, and the application fetching the result](https://developers.cloudflare.com/cdn-cgi/image/onerror=redirect,width=4160,height=4000,format=webp/_astro/openai-agents-api-arch.CCqSDnZe.jpg)

The [template ↗︎](https://github.com/cloudflare/sandbox-sdk/tree/main/openai/agents-api) 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 ↗︎](https://nodejs.org/) or later and a running [Docker ↗︎](https://www.docker.com/) 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`.

1. Set your OpenAI API key, then create an agent:

```bash
export OPENAI_API_KEY="<OPENAI_API_KEY>"
```

```bash
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:

```bash
export OPENAI_AGENT_ID="agent_..."
```

2. Generate a shared secret for the container cleanup endpoint, and save it:

   ```bash
   openssl rand -hex 32
   ```

   Select **Deploy to Cloudflare**:

   [![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/sandbox-sdk/tree/main/openai/agents-api)

   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 ↗︎](https://platform.openai.com/settings/project/webhooks), 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 `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:

```bash
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:

```bash
export SESSION_ID="sess_..."
```

Open the session event stream in one terminal:

```bash
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:

```bash
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:

1. 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/openai/agents-api
   npm install
   npx wrangler login
   ```


2. Generate a shared secret for the container cleanup endpoint, and save it:

   ```bash
   openssl rand -hex 32
   ```


3. 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
   ```


4. 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:

```bash
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 ↗︎](https://github.com/cloudflare/sandbox-sdk/tree/main/openai/agents-api/basic) 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:

```bash
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:

```bash
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

1. The application creates or retrieves an OpenAI session and sends input through the Agents API.
2. When `EXECUTOR_PREWARM_ENABLED` is `true`, as in the template, an `agent.session.created` webhook makes the Worker retrieve the session and start its container with the environment ID and remote URL of the session.
3. An `agent.session.action_required` webhook 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.
4. The Durable Object named for the session starts a container with those connection details and the executor key. `codex exec-server` connects out to OpenAI.
5. Each container start, environment connection, and `agent.session.in_progress` webhook resets a deadline of `EXECUTOR_KEEP_ALIVE_SECONDS`. When the deadline passes, the Worker retrieves the session. If the session is still active, the Worker sets a new deadline.
6. An `agent.session.idle` webhook makes the Worker snapshot the container and reset the deadline. When the deadline passes, the Worker stops the container and keeps the snapshot.
7. New input sends another `agent.session.action_required` webhook. The Worker reuses a running container for the same environment ID, or starts the next environment from the saved snapshot.

![Lifecycle showing an application creating an Agents API session, OpenAI sending webhooks to Cloudflare, and the container connecting its Codex executor to OpenAI](https://developers.cloudflare.com/cdn-cgi/image/onerror=redirect,width=5928,height=3104,format=webp/_astro/openai-agents-api-lifecycle.BOaDc5z5.jpg)

## 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](https://developers.cloudflare.com/sandbox/concepts/lifetime/#snapshots-carry-files-to-the-next-instance).

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](https://developers.cloudflare.com/sandbox/files/mount-an-r2-bucket/).

## Add tools to the container

The `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:

```dockerfile
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 `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_SECRET` stay in Worker secrets. The executor key goes into the container as `CODEX_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-server` can 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](https://developers.cloudflare.com/containers/configuration/outbound-traffic/).
- OpenAI must reach `/webhook` without an interactive Access login. The Worker checks the signature on each webhook, and the cleanup endpoint requires `EXECUTOR_CLIENT_SECRET`. If you protect other routes with Cloudflare Access, use [path-specific policies](https://developers.cloudflare.com/cloudflare-one/access-controls/policies/app-paths/) that leave `/webhook` reachable.

For more information about what a sandbox exposes to the code inside it, refer to [Sandbox security](https://developers.cloudflare.com/sandbox/concepts/security/).

## Related resources

- [OpenAI Agents API template ↗︎](https://github.com/cloudflare/sandbox-sdk/tree/main/openai/agents-api)
- [OpenAI Agents API documentation ↗︎](https://developers.openai.com/api/docs/guides/agents-api/overview)
- [OpenAI Python Cloudflare webhook example ↗︎](https://github.com/OpenAI/agents-api-python-preview/tree/main/examples/self_hosted_sandbox/webhook_managed/cloudflare)
- [OpenAI TypeScript Cloudflare webhook example ↗︎](https://github.com/OpenAI/agents-api-typescript-preview/tree/main/examples/self_hosted_sandbox/webhook_managed/cloudflare)
- [Cloudflare Containers](https://developers.cloudflare.com/containers/)

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@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-09-30","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/"}}
```
