OpenAI Agents SDK
In this guide you'll build a support agent with the OpenAI Agents SDK whose refund tool pauses for human approval above $100. Python, about ten minutes.
Before you start
- A Confirm account and API key. Sign in, open Dashboard → API Keys, create a key, and copy it (it's shown once).
- An OpenAI API key.
- Python 3.10+.
terminal
pip install openai-agents requests.env / shell
export CONFIRM_API_KEY=cfm_live_...
export OPENAI_API_KEY=sk-...Step 1: the approval helper
One function handles the Confirm side: create the request, poll every 5 seconds, return the verdict. It's the same file used in the LangChain guide; reuse it if you have it.
confirm_client.py
import os
import time
import requests
BASE = "https://confirm.dev/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['CONFIRM_API_KEY']}"}
def require_approval(summary: str, payload: dict, notify: str, ttl_minutes: int = 60, **fields) -> dict:
"""Create an approval request and block until a human decides.
Returns the resolved request: check ["status"] and execute ["effectivePayload"],
which includes any edits the human made.
"""
res = requests.post(
f"{BASE}/requests",
headers=HEADERS,
json={"summary": summary, "payload": payload, "notify": notify,
"ttlMinutes": ttl_minutes, **fields}, # reasoning, recentActions, agentState
timeout=30,
)
res.raise_for_status()
request = res.json()
while True:
time.sleep(5)
current = requests.get(
f"{BASE}/requests/{request['id']}", headers=HEADERS, timeout=30
).json()
if current["status"] != "PENDING":
return currentStep 2: gate the tool
The Agents SDK turns any decorated function into a tool. The docstring is what the model reads, so it says plainly that approval takes time and denial is a valid outcome.
agent.py
from agents import Agent, Runner, function_tool
from confirm_client import require_approval
@function_tool
def refund(customer_id: str, amount: float) -> str:
"""Refund a customer in USD.
Refunds over $100 require human approval: this tool blocks until someone
decides, and may return a denial with the reviewer's reason. Do not retry
a denied refund.
"""
if amount <= 100:
return issue_refund(customer_id, amount) # your real refund logic
verdict = require_approval(
summary=f"Refund ${amount:,.2f} to customer {customer_id}",
payload={"customerId": customer_id, "amount": amount},
notify="group:finance" if amount > 1000 else "support-lead@company.com",
reasoning="Customer reported a duplicate charge; order history supports the refund.",
recentActions=["Read the support ticket", "Checked the customer's order history"],
)
if verdict["status"] != "APPROVED":
note = verdict.get("resolutionNote") or verdict["status"].lower()
return f"Refund denied by human reviewer: {note}"
approved = verdict["effectivePayload"] # the human may have changed the amount
return issue_refund(approved["customerId"], approved["amount"])
agent = Agent(
name="Support agent",
instructions=(
"You handle customer support. Use the refund tool when a refund is "
"warranted. If a refund is denied, explain the reviewer's reason."
),
tools=[refund],
)
result = Runner.run_sync(
agent, "Customer #4821 was double-charged $180. Refund the duplicate charge."
)
print(result.final_output)Step 3: run it and decide
terminal
python agent.pyWhat happens, in order:
- The model calls
refundwith $180. Over the threshold, so Confirm is asked. - The run pauses inside the tool. Your terminal sits quiet: that's the pause working.
- The
notifyinbox gets "Action required: Refund $180.00 to customer #4821". Open the link, inspect the payload. - Try Edit payload first: change the amount to
90, add a note, approve. - Within 5 seconds the tool resumes with your verdict and refunds
effectivePayload: $90, not $180. The agent's final output reflects what actually happened.
GUARDRAILS VS APPROVALS
The Agents SDK's built-in guardrails validate inputs and outputs with code or another model. Confirm is the complement: a human decision on the action itself, with an audit trail of who approved what. Use guardrails to filter, Confirm to authorize.
Next steps
- Webhooks for event-driven resumption instead of polling.
- Approval groups to escalate by threshold.
- The API reference for
metadata: attach the run ID so the audit log ties back to your traces.