Managing secrets in the OpenAI Agents SDK
Frameworks
The OpenAI Agents SDK reads OPENAI_API_KEY and OPENAI_BASE_URL from the
environment. Both the zero-code setup and the proxy setup are configuration
only. For local development:
seekrit secrets import .env --app agent --env development && rm .env
seekrit run -- python agent.py
The one thing specific to this SDK is the tracing exporter. It uses the same API key as the model, to a different path. Skip that section and traces stop the day you add a proxy.
Setup 1: wrap the process
seekrit run -- python agent.py
from agents import Agent, Runner
agent = Agent(name="Assistant", instructions="You are a helpful assistant")
result = Runner.run_sync(agent, "Write a haiku about recursion.")
print(result.final_output)
No seekrit code. The key arrives in the environment the SDK already reads.
Delete the .env afterwards. A file that still exists is a file
an agent can still read.
Setup 2: resolve in code
import seekrit
from agents import set_default_openai_key
set_default_openai_key(seekrit.Client().get("OPENAI_API_KEY"))
Call it before the first model call. For a custom client, with a different
endpoint, an org header, or a shared httpx client:
import seekrit
from openai import AsyncOpenAI
from agents import set_default_openai_client
secrets = seekrit.Client().resolve()
set_default_openai_client(AsyncOpenAI(api_key=secrets["OPENAI_API_KEY"]))
Pass either openai_client or api_key/base_url. Passing both raises
UserError.
Setup 3: the process never holds the key
Environment only, no source change:
export OPENAI_BASE_URL=http://127.0.0.1:8080/openai/v1
export OPENAI_API_KEY='{{seekrit:OPENAI_API_KEY}}'
The SDK sends the placeholder. The seekrit proxy swaps it for the real key against a default-deny rule list:
# seekrit-proxy.toml
listen = "127.0.0.1:8080"
[[route]]
prefix = "/openai"
upstream = "https://api.openai.com"
allow = ["OPENAI_API_KEY"]
methods = ["POST"]
paths = ["/v1/responses", "/v1/chat/completions"]
Nothing in the agent's process, prompt, or tool output contains the key. A prompt injection that asks the agent to print its environment gets the placeholder.
Without running the proxy
set_default_openai_client takes a client, and a client takes a transport:
import httpx
from openai import AsyncOpenAI
from agents import set_default_openai_client
from seekrit.transport import AsyncSeekritTransport
set_default_openai_client(
AsyncOpenAI(
api_key="{{seekrit:OPENAI_API_KEY}}",
http_client=httpx.AsyncClient(
transport=AsyncSeekritTransport(allow={"api.openai.com": ["OPENAI_API_KEY"]}),
),
)
)
This runs in your process, so it is a weaker boundary than the proxy. The in-process injection page sets out the trade-off.
Why did tracing stop when I added the proxy?
The SDK exports traces to OpenAI by default, with your API key. Behind the proxy the trace upload carries the placeholder too, to a path the route above does not allow, so the proxy denies it. Pick one:
- Allow the trace ingest path in the route.
- Give tracing its own credential with
set_tracing_export_api_key(...). - Turn it off with
set_tracing_disabled(True).
Two related notes. OPENAI_WEBSOCKET_BASE_URL does not follow
OPENAI_BASE_URL, and the proxy substitutes on HTTP requests, so do not put
a placeholder in the realtime path. And handoffs share the process: every
agent in a handoff chain sees the same environment. Per-agent credential
separation needs the proxy's session tickets, not a second variable.
Which setup for which credential
| Credential | Setup |
|---|---|
| Model key, local development | 1 |
| Model key, your own deployment | 2, or 3 if the agent runs generated code |
| A tool credential (Stripe, GitHub, a database) | 3 with methods and paths set |
| Agents in a handoff chain with different reach | 3 with session tickets |
Setup
Three commands put the keys in an environment, mint a token bound to it, and
export SEEKRIT_TOKEN. The
frameworks guide has
them. Values are encrypted before they are stored, so the service holding
them cannot read them. The full page is at
/docs/guides/frameworks/openai-agents.