Agents API — environments, sandboxes, files, artifacts, vaults and credentials
Status: DOCUMENTED · BETA (OpenAI-Beta: agents=v1) · LIVE_VERIFIED: environment-template create/retrieve/update/delete (201/200/200/200, examples/openai/agents/environment_template_create.*), GET /v1/agents/environments/templates, GET /v1/vaults, GET /v1/agents/sessions/{id}/artifacts (200, empty); LIVE_DISCOVERED: GET /v1/agents/environments/{bogus}, GET /v1/vaults/{bogus} → 404 not_found_error (routes confirmed). Vault/credential creation, hosted-sandbox sessions and live file operations not exercised (no credentials are ever created by this atlas; no container cost incurred).
Sources: OpenAI-hosted sandboxes · Self-hosted sandboxes · Sandbox lifecycle · Files and artifacts · Sandbox security · Plugins · MCP · Vaults · provider guides (environments/providers/{modal,cloudflare,vercel,daytona,blaxel,e2b,runloop,digitalocean,oci}) · OpenAPI openapi-master.yaml.
Last verified: 2026-09-18.
1. Environment types (session.environment)
| Type | Request (EnvironmentParam) |
Response (EnvironmentResource) |
Notes |
|---|---|---|---|
none |
{type:"none"} |
{type:"none"} |
No filesystem/shell; initial input required; remote MCP (service) + function tools only |
openai_hosted |
`type, packages{python[],system[],npm[]}, setup_commands[{command,cwd}] (≤16, confidential), network{access: enabled·disabled·restricted, allowed_domains[] (1–100 exact hosts)}, env{} (PATH/CODEX_*/OPENAI_API_KEY rejected), capability_directories[], skills[] (≤200: skill_reference{skill_id,version} | inline{name,description,source{type:base64,media_type:application/zip,data}}), plugins[] (≤32 inline zips), files[] (≤50: file_id{file_id,path} | inline{data,path}), environment_template_id` |
self_hosted |
{type, workspace_directory (required, absolute), capability_directories[]} |
{type, id, remote_url, workspace_directory, capability_directories} |
You run codex exec-server; remote_url must be passed unchanged |
Environment status (GET /v1/agents/environments/{environment_id} → agent.environment): pending → connected | disconnected | expired | failed; session-event state (SessionEnvironmentStateResource.status): pending · ready · connected · disconnected · failed (+ error{type,code,message}). Wait for connected before file operations.
2. OpenAI-hosted sandbox lifecycle
POST /v1/agents/sessionswithenvironment.type: openai_hosted→ response means setup started; stream showsagent.session.environment.pending → ready → connected(orfailed, readenvironment.error).agent.session.environment.reset(environment_id,reset_count) signals the sandbox was replaced — conversation survives, files/processes do not.- Files persist across turns while the sandbox exists;
/workspace/outputs/**is published as immutable artifacts at turn completion. - Keep-alives run between turns; deletable after ~1 h of inactivity (not configurable). Sandbox compute billed at container rates.
DELETE /v1/agents/sessions/{id}requests cleanup;409while busy → retry with a cap. Closing an event stream does not cancel work.
Live files API (connected hosted environments)
GET /v1/agents/environments/{environment_id}/files?path=&limit=&order=&page=→{object:"page", data:[{object:"agent.environment.file", environment_id, path, size_bytes}], next, has_more}(token pagination, not cursor).POST /v1/agents/environments/{environment_id}/filesbodyHostedEnvironmentFileParam(file_id{file_id,path}orinline{data,path}) →201 agent.environment.file.
Artifacts (agent.session.artifact)
GET /v1/agents/sessions/{id}/artifacts?environment_id=&limit=&order=&after=→ list; fieldsid, session_id, environment_id, turn_id, path, size_bytes, created_at.GET …/artifacts/{artifact_id}metadata ·GET …/artifacts/{artifact_id}/content→application/octet-stream·DELETE …/artifacts/{artifact_id}→agent.session.artifact.deleted(environment file untouched).- Versioning = (turn_id, path). Not uploadable/editable through the API. Self-hosted files are never published as artifacts.
- Limits: 50 files/create request; inline 5 MiB/file, 10 MiB total; Files-API copy 50 MiB; artifact 200 MiB; outputs per turn 500 MiB. Spec maxLength of inline
datais 6 990 508 chars (≈5 MiB base64).
3. Environment templates (agent.environment.template, hosted only)
POST/GET/POST/DELETE /v1/agents/environments/templates[/{environment_template_id}] — same fields as openai_hosted minus environment_template_id, plus name (1–256). Returned resource exposes safe metadata only: setup_commands and env are never returned; inline skills/plugins/files appear without contents (HostedTemplate*Resource). Sessions reference a template with environment.environment_template_id; omitted inline fields inherit, supplied arrays replace (e.g., plugins). Templates save configuration, not a running workspace. Update: omit to keep, null to clear (network: null resets to the default policy). Delete → {object:"agent.environment.template.deleted"}.
4. Self-hosted environments and the executor
- Prepare:
mkdir -p /workspace && npm install -g @openai/codex@alpha; allow egress tohttps://api.openai.com(registration) andwss://codex-cloud-environments.chatgpt.com(commands/results). All connections are outbound; executor reconnects automatically. - Keys: application key (
api.agents.*,api.responses.write, optionallyapi.vaults.*) stays outside the sandbox; an environment key (dashboard Agents → Environments → Keys, same org/project/owner, all other permissions None) goes in asCODEX_API_KEY. Agent code can read it, but it only authorizes environment connections. - Start:
codex exec-server --remote "<session.environment.remote_url>" --environment-id "<session.environment.id>". Webhookagent.session.createdcarriesdata.environment_id,data.environment_type,data.connect.remote_url(docs examplehttps://api.openai.com/v1/agents/api). - Flow: create session (no
inputneeded) → open event stream → start executor → sendagent.session.input.message. Connection states:environment.pending → connected/failed. When input arrives with a disconnected executor the API addsrequired_actions[{type:"environment_connection", environment_id}], emits webhookagent.session.action_requiredand streamagent.session.requires_actionbefore waiting up to 5 minutes; if it expires the submission fails (initial input can leave the sessionfailed). - Lifecycle rules: one component per session's environment (no duplicates), keep compute between turns or apply a grace period, idle event is not a safe shutdown signal, reusing the environment ID does not restore files, deleting a session neither stops compute nor emits a webhook. Provider recipes: Modal, Cloudflare, Vercel, Daytona, Blaxel, E2B, Runloop, DigitalOcean, OCI (cookbook
examples/agents_api/sandboxes/{application_managed,webhook_managed}).
5. Tools that depend on the environment
| Tool | none |
openai_hosted |
self_hosted |
|---|---|---|---|
| Bash / apply_patch (built-in) | no | yes | yes |
mcp http connection_origin: service (default) |
yes | yes | yes |
mcp http connection_origin: environment |
no | yes | yes |
mcp stdio (command, absolute cwd, args, env_vars; hosted needs network enabled) |
no | yes | yes |
Skills / plugins (capability_directories, skills[], plugins[]) |
no | yes (upload zips / skill_reference to /v1/skills) |
yes (directories) |
web_search, function, tool_search, programmatic_tool_calling |
yes | yes | yes |
Plugin package: .codex-plugin/plugin.json (name, version, description, skills: "./skills/", mcpServers: "./.mcp.json"), .mcp.json (mcpServers.<label>{type:http,url} / stdio with env_vars, bearer_token_env_var, literal http_headers; env_http_headers unsupported), skills/<name>/SKILL.md (frontmatter name, description). Hosted upload: one ZIP per plugin with name/description matching the manifest. Existing sessions do not reload changed plugins.
6. Vaults and credentials
Scopes: api.vaults.read (list/retrieve), api.vaults.write (create/rotate/delete). Header OpenAI-Beta: agents=v1 (live). SDK: client.beta.agents.vaults.*, client.beta.agents.vaults.credentials.*.
| Endpoint | Body / params | Returns |
|---|---|---|
POST /v1/vaults |
{name (1–256 bytes after trim), metadata{}} |
201 vault {id, object:"vault", name, metadata, created_at} |
GET /v1/vaults |
order, limit (1–100, default 20), status (active·archived, or status[]=), after |
list |
GET /v1/vaults/{vault_id} · DELETE |
vault · vault.deleted (deletes all credentials; does not revoke provider tokens or stop running sessions) |
|
POST /v1/vaults/{vault_id}/credentials |
{name, auth} where auth = static_bearer{mcp_server_url, token} · `mcp_oauth{mcp_server_url, access_token, expires_at (RFC 3339), refresh{token_endpoint, client_id, refresh_token, resource?, scope?, token_endpoint_auth{type: none |
client_secret_basic{client_secret} |
GET …/credentials (status, cursor) · GET …/credentials/{credential_id} |
secrets never returned | |
POST …/credentials/{credential_id} (rotate) |
`{auth: static_bearer{token} | mcp_oauth{access_token?, expires_at? (null clears), refresh{refresh_token?, scope?, token_endpoint_auth{client_secret?}}} |
DELETE …/credentials/{credential_id} |
vault.credential.deleted |
Usage: attach vault_ids: ["vault_…"] on session creation; MCP tool transport.server_url must match the credential's mcp_server_url; if several match, set the tool's credential_id. Vaults apply only to connection_origin: service; environment-origin HTTP needs inline transport.authorization/headers (encrypted, omitted from responses) or a trusted proxy; stdio servers read env vars listed in transport.env_vars. environment_variable credentials (hosted sandboxes only): the sandbox gets a placeholder in $SECRET_NAME; the egress proxy substitutes the real value only for allowed HTTPS destinations (networking.limited.allowed_hosts or unrestricted — the latter requires environment.network.access: restricted with explicit allowed_domains). The environment network policy must also permit those hosts.
Security guidance (docs): isolate workloads (VMs, one project per app), restrict egress to the executor hosts + needed MCP servers, keep app keys out of images/source/logs, broker third-party credentials via a proxy, rotate/revoke on suspicion.
7. Live verification (2026-09-18)
| Call | Result |
|---|---|
GET /v1/agents/environments/templates?limit=5 |
200 {object:list, data:[]} |
GET /v1/agents/environments/env_doesnotexist |
404 not_found_error "No managed agent resource found: env_doesnotexist" |
GET /v1/vaults?limit=5 |
200 empty list |
GET /v1/vaults/vault_doesnotexist |
404 not_found_error "No vault resource found: vault_doesnotexist" |
GET /v1/agents/sessions/{id}/artifacts (env none) |
200 empty list |
POST /v1/agents/environments/templates (packages.python, setup_commands, network restricted + allowed_domains, env, inline file) |
201 agent.environment.template (envtmpl_…); response omits setup_commands/env; inline file echoed as {type, path, size_bytes: 8} |
POST …/templates/{id} (name only) · GET …/templates/{id} · DELETE …/templates/{id} |
200 (network preserved) · 200 · 200 agent.environment.template.deleted |
| Vault create, credential create, hosted sandbox session, live files | not run (policy: no credential creation; no container spend) |