> ## Documentation Index
> Fetch the complete documentation index at: https://boxrouter.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Use the OpenAI Agents API

> Connect an OpenAI Agents API session to an executor running in a BoxRouter sandbox.

The OpenAI Agents API runs the agent on OpenAI's side. `codex exec-server`, running in your BoxRouter sandbox, connects out to that session and executes its commands and file operations there. Each session needs its own executor, so each gets its own sandbox.

This is OpenAI's hosted service. For the Python library that runs in your process, see the [OpenAI Agents SDK](https://boxrouter.ai/docs/integrations/openai-agents-sdk.md).

**Before You Begin:** A BoxRouter API key and CLI, an OpenAI project key, and an environment key saved as a workspace secret.

## Before You Begin

* [Set Up Your Account](https://boxrouter.ai/docs/setup.md) and install the [CLI](https://boxrouter.ai/docs/cli.md#install).
* Create an OpenAI project key with `api.agents.read`, `api.agents.write` and `api.responses.write`. It stays in your application.
* In the OpenAI dashboard's **Agents** tab, create an environment key. It must belong to the session's organization and project, and to its user or service account. Set every other permission to None.
* Save the environment key as the workspace secret `CODEX_API_KEY`. It authorizes only the executor's connection:

```python Python theme={"theme":"css-variables"}
from getpass import getpass

from boxrouter import BoxRouter
from boxrouter.models import PutSecret

with BoxRouter() as client:
    client.api.secrets.put("CODEX_API_KEY", body=PutSecret(value=getpass("CODEX_API_KEY: ")))
```

```typescript TypeScript theme={"theme":"css-variables"}
import { BoxRouter } from '@boxrouter/sdk';

const value = process.env.CODEX_API_KEY;
if (!value) throw new Error('Set CODEX_API_KEY in the environment.');

const client = new BoxRouter();
try {
  await client.api.secrets.put({ path: { name: 'CODEX_API_KEY' }, body: { value } });
} finally {
  await client.close();
}
```

```bash CLI theme={"theme":"css-variables"}
boxrouter secrets set CODEX_API_KEY
```

Python reads the key at a hidden prompt, as the CLI does; TypeScript reads it from the environment.

The sandbox needs outbound HTTPS to `api.openai.com` and WebSocket access to `codex-cloud-environments.chatgpt.com`. It takes no inbound connection. OpenAI's [self-hosted guide](https://developers.openai.com/api/docs/guides/agents-api/environments/self-hosted) has the full permission list.

## Advanced: Create the Session

Install the OpenAI SDK with `pip install openai`. Set `OPENAI_API_KEY` to your project key and `OPENAI_AGENT_MODEL` to a model your project can use. Save this as `openai-session.py`:

```python theme={"theme":"css-variables"}
import os
from openai import OpenAI

PROMPT = (
    "Run uname -a and write the output to /tmp/boxrouter-openai-uname.txt. "
    "Make the file readable by all sandbox users."
)
MESSAGE = {
    "type": "agent.session.input.message",
    "input": [{"role": "user", "content": [{"type": "input_text", "text": PROMPT}]}],
}

with OpenAI() as client:
    session = client.beta.agents.sessions.create(
        agent={"model": os.environ["OPENAI_AGENT_MODEL"]},
        environment={"type": "self_hosted", "workspace_directory": "/tmp"},
    )
    with client.beta.agents.sessions.events.stream(session.id) as events:
        print("Session ID:", session.id, flush=True)
        print("Environment ID:", session.environment.id, flush=True)
        print("Remote URL:", session.environment.remote_url, flush=True)
        sent = False
        for event in events:
            if event.type == "agent.session.environment.connected" and not sent:
                client.beta.agents.sessions.events.create(session.id, events=[MESSAGE])
                sent = True
            elif event.type == "agent.session.turn.output_text.delta":
                print(event.delta, end="", flush=True)
            elif event.type == "agent.session.turn.completed" and event.turn.subagent_id is None:
                print("\nAgent turn completed.")
                break
            elif event.type in ("agent.session.environment.failed", "agent.session.failed"):
                raise RuntimeError(f"Session event: {event.type}")
            elif event.type in ("agent.session.turn.failed", "agent.session.turn.cancelled"):
                if event.turn.subagent_id is None:
                    raise RuntimeError(f"Session event: {event.type}")
            elif event.type == "error":
                raise RuntimeError(event.error.message)
        else:
            raise RuntimeError("Stream closed before the root turn completed; retrieve the session state.")
```

Run `python openai-session.py` in one terminal and leave it open. This example uses `/tmp` as a writable workspace; choose a suitable directory for your own work. In a second terminal, export `OPENAI_REMOTE_URL` and `OPENAI_ENVIRONMENT_ID` using the printed values, then launch the executor below. Keep the remote URL unchanged for reconnects.

## Start the Executor

Export `OPENAI_REMOTE_URL` and `OPENAI_ENVIRONMENT_ID` from the session, then launch. This manifest is the `openai-agents-environment` catalog entry with your session's values filled in:

```python Python theme={"theme":"css-variables"}
import os

from boxrouter import BoxRouter
from boxrouter.models import AgentManifest, LaunchEnvironment

manifest = AgentManifest(
    name="openai-agents-environment",
    interactive=False,
    setup=['npm install --global --prefix "$HOME/.local" @openai/codex@0.164.0-alpha.10'],
    command=(
        '"$HOME/.local/bin/codex" exec-server'
        ' --remote "$OPENAI_REMOTE_URL"'
        ' --environment-id "$OPENAI_ENVIRONMENT_ID"'
    ),
    env=LaunchEnvironment.from_dict({
        "OPENAI_REMOTE_URL": os.environ["OPENAI_REMOTE_URL"],
        "OPENAI_ENVIRONMENT_ID": os.environ["OPENAI_ENVIRONMENT_ID"],
    }),
    secrets=["CODEX_API_KEY"],
    lifetime_seconds=1800,
)

with BoxRouter() as client:
    box = client.sandboxes.launch(manifest=manifest)
    print(f"Sandbox {box.id} started")
```

```typescript TypeScript theme={"theme":"css-variables"}
import { BoxRouter } from '@boxrouter/sdk';

const { OPENAI_REMOTE_URL, OPENAI_ENVIRONMENT_ID } = process.env;
if (!OPENAI_REMOTE_URL || !OPENAI_ENVIRONMENT_ID) {
  throw new Error('Set OPENAI_REMOTE_URL and OPENAI_ENVIRONMENT_ID.');
}

const client = new BoxRouter();
try {
  const box = await client.sandboxes.launch({
    manifest: {
      name: 'openai-agents-environment',
      interactive: false,
      setup: ['npm install --global --prefix "$HOME/.local" @openai/codex@0.164.0-alpha.10'],
      command:
        '"$HOME/.local/bin/codex" exec-server' +
        ' --remote "$OPENAI_REMOTE_URL"' +
        ' --environment-id "$OPENAI_ENVIRONMENT_ID"',
      env: { OPENAI_REMOTE_URL, OPENAI_ENVIRONMENT_ID },
      secrets: ['CODEX_API_KEY'],
      lifetime_seconds: 1800,
    },
  });
  console.log(`Sandbox ${box.id} started`);
} finally {
  await client.close();
}
```

```bash CLI theme={"theme":"css-variables"}
boxrouter launch --manifest - <<EOF_MANIFEST
name: openai-agents-environment
interactive: false
setup:
  - npm install --global --prefix "\$HOME/.local" @openai/codex@0.164.0-alpha.10
command: >-
  "\$HOME/.local/bin/codex" exec-server
  --remote "\$OPENAI_REMOTE_URL"
  --environment-id "\$OPENAI_ENVIRONMENT_ID"
env:
  OPENAI_REMOTE_URL: "$OPENAI_REMOTE_URL"
  OPENAI_ENVIRONMENT_ID: "$OPENAI_ENVIRONMENT_ID"
secrets: [CODEX_API_KEY]
lifetime_seconds: 1800
EOF_MANIFEST
```

The last line of output is `Sandbox SANDBOX_ID started`; the SDK examples print only that line. The executor is already running; do not start another. Running a process does not extend the sandbox's lifetime, so set `lifetime_seconds` to cover the whole session.

`boxrouter launch openai-agents-environment` on its own is refused because `OPENAI_REMOTE_URL` and `OPENAI_ENVIRONMENT_ID` are unset, and the CLI has no flag to supply them to the catalog entry. Over HTTP, send them as `overrides.env` in [Create a launch](https://boxrouter.ai/docs/reference/launch-create.md), with the secret already saved:

```json theme={"theme":"css-variables"}
{
  "agent": "openai-agents-environment",
  "overrides": {
    "env": {
      "OPENAI_REMOTE_URL": "<session.environment.remote_url>",
      "OPENAI_ENVIRONMENT_ID": "<session.environment.id>"
    },
    "lifetime_seconds": 1800
  }
}
```

## Check That It Worked

The first terminal prints the agent's response and `Agent turn completed.` once the task finishes. A connection alone does not confirm that the task worked. Replace `SANDBOX_ID` with the ID printed at launch and read the file:

```python Python theme={"theme":"css-variables"}
import sys

from boxrouter import BoxRouter

with BoxRouter() as client:
    box = client.sandboxes.get("SANDBOX_ID")
    sys.stdout.buffer.write(box.files.read("/tmp/boxrouter-openai-uname.txt"))
```

```typescript TypeScript theme={"theme":"css-variables"}
import { BoxRouter } from '@boxrouter/sdk';

const client = new BoxRouter();
try {
  const box = await client.sandboxes.get('SANDBOX_ID');
  process.stdout.write(await box.files.read('/tmp/boxrouter-openai-uname.txt'));
} finally {
  await client.close();
}
```

```bash CLI theme={"theme":"css-variables"}
boxrouter read SANDBOX_ID --path /tmp/boxrouter-openai-uname.txt
```

This sample file is readable by other sandbox users, so do not put secrets in it.

If the stream reports `agent.session.environment.failed`, the connection failed. Check the launch output
for setup errors and confirm that the sandbox can reach the OpenAI endpoints listed above.

The executor can reconnect automatically if the connection drops. Check the session and sandbox before
starting a replacement, so two executors do not connect to the same session. A disconnect can interrupt
a tool even if the turn completes; inspect the tool results and session before retrying work.

If the executor has stopped and you need a new sandbox, retrieve any files you need. Request and confirm
deletion of the old sandbox before launching a replacement. Use the same `OPENAI_REMOTE_URL` and
`OPENAI_ENVIRONMENT_ID`. [Check cleanup](https://boxrouter.ai/docs/errors.md#when-cleanup-fails) if deletion is pending or uncertain.
The new sandbox starts without the old one's files.

## Clean Up

Retrieve the files you need, [delete the OpenAI session](https://developers.openai.com/api/docs/guides/agents-api/sessions/manage#delete-a-session), then destroy the sandbox:

```python Python theme={"theme":"css-variables"}
from boxrouter import BoxRouter

with BoxRouter() as client:
    client.sandboxes.get("SANDBOX_ID").destroy()
```

```typescript TypeScript theme={"theme":"css-variables"}
import { BoxRouter } from '@boxrouter/sdk';

const client = new BoxRouter();
try {
  await (await client.sandboxes.get('SANDBOX_ID')).destroy();
} finally {
  await client.close();
}
```

```bash CLI theme={"theme":"css-variables"}
boxrouter destroy SANDBOX_ID
```

## One Session per Sandbox

Create a separate OpenAI session and sandbox for each concurrent workload, and keep the mapping in your application. The catalog entry sets `max_copies: 1`, because [copies](https://boxrouter.ai/docs/launch/copies.md) share one manifest's values and would connect several executors to the same session. OpenAI's [sandbox lifecycle](https://developers.openai.com/api/docs/guides/agents-api/environments/lifecycle) covers reconnection.

## Next Steps

> [card] [Supply an Agent's Secrets](https://boxrouter.ai/docs/launch/secrets.md)
>
> How the environment key reaches the sandbox.

> [card] [Use the OpenAI Agents SDK](https://boxrouter.ai/docs/integrations/openai-agents-sdk.md)
>
> The Python library instead.

> [card] [Choose an Agent Integration](https://boxrouter.ai/docs/integrations/overview.md)
>
> Compare the integrations for your agent stack.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.