The OpenAI Agents SDK tool is the caller. It reads an access token from run context and sends Authorization: Bearer to your API. require_auth on that API is the gate. The tool does not decide who the caller is, and it does not invent a subject.
Sample: openai-agents/python. Python 3.10+. Package openai-agents==0.24.0 (import agents).
Hold the token in context
@dataclass(frozen=True)
class Caller:
token: str
async def fetch_subject(token: str, client: httpx.AsyncClient, url: str) -> str:
headers: dict[str, str] = {}
if token:
headers["Authorization"] = f"Bearer {token}"
response = await client.get(url, headers=headers)
response.raise_for_status()
return str(response.json()["subject"])
def build_agent(client: httpx.AsyncClient, url: str) -> Agent:
@function_tool
async def whoami(ctx: RunContextWrapper[Caller]) -> str:
"""Return the Authdog subject for this caller. Do not invent one."""
return await fetch_subject(ctx.context.token, client, url)
return Agent(
name="authdog-caller",
instructions="Call whoami. Never invent a subject.",
tools=[whoami],
)Caller.token is the access token from the client-credentials exchange. The client secret stays in the process that minted it. The model sees the tool result, which is the subject string the API returned.
Run it by passing context=Caller(token=...) to Runner.run. OPENAI_API_KEY is required for that run. Proving the tool attaches the header does not require a model: call fetch_subject directly.
The route the tool calls
authdog = Authdog(public_key=public_key)
@app.get("/whoami")
async def whoami(user=Depends(authdog.require_auth)):
return {"subject": subject_of(user)}Authdog comes from authdog.fastapi. A missing or rejected token is 401 from this route, including when the tool sends an empty header because Caller.token is empty.
To act for a person, keep this route and this tool. Put an RFC 8693 token in Caller.token so sub stays the user and act names the agent. The samples mint a client_credentials token whose subject is the agent.
What you should see
GET /whoamiwith no token returns401.- The same call with a bearer userinfo accepts returns
{"subject": "<id>"}. Runner.run(..., context=Caller(token=os.environ["AUTHDOG_ACCESS_TOKEN"]))with instructions to callwhoamiprints that subject as the model output.
pytest covers the route and fetch_subject. It does not call Runner.
Configure
| Variable | Process | Purpose |
|---|---|---|
PK_AUTHDOG |
API | Public key for require_auth. |
WHOAMI_URL |
Agent | Absolute URL of GET /whoami. |
AUTHDOG_ACCESS_TOKEN |
Agent | Bearer to place in Caller. |
AUTHDOG_CLIENT_ID / AUTHDOG_CLIENT_SECRET |
Minting process | Only if you exchange a token yourself. |
OPENAI_API_KEY |
Agent | Only for Runner.run. |
uvicorn tool_api:create_app --factory --port 8000Related
| Read | To learn how to |
|---|---|
| LangGraph | Protect the workflow the tool is calling |
| Agent delegation | Exchange a user token when the agent acts for a person |
| Agents quickstart | Register the client that mints AUTHDOG_ACCESS_TOKEN |