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

  1. A Confirm account and API key. Sign in, open Dashboard → API Keys, create a key, and copy it (it's shown once).
  2. An OpenAI API key.
  3. 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 current

Step 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.py

What happens, in order:

  1. The model calls refund with $180. Over the threshold, so Confirm is asked.
  2. The run pauses inside the tool. Your terminal sits quiet: that's the pause working.
  3. The notify inbox gets "Action required: Refund $180.00 to customer #4821". Open the link, inspect the payload.
  4. Try Edit payload first: change the amount to 90, add a note, approve.
  5. Within 5 seconds the tool resumes with your verdict and refunds effectivePayload: $90, not $180. The agent's final output reflects what actually happened.
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.