Add Phone Calls to Your OpenAI Agent

Add Phone Calls to Your OpenAI Agent

6 min read
Yanis Mellata
Integrations

What You're Building

You have an agent built with the OpenAI Agents SDK. It handles a multi-step workflow — qualifying leads, managing appointments, coordinating vendors, whatever. At some point, the workflow requires a phone call to a business.

By the end of this post, your agent will have a place_phone_call function tool that dials a real number, has a real conversation, and returns structured data. The agent calls it like any other tool.

Prerequisites:

  • Python 3.10+
  • pip install openai-agents httpx
  • An AgentPhone API key (get one free — 5 calls, no credit card)

The Phone Tool

The OpenAI Agents SDK turns any Python function into a tool with the @function_tool decorator. Here's the complete AgentPhone tool:

import httpx
import time
import os
from agents import function_tool

AGENTPHONE_API_KEY = os.environ["AGENTPHONE_API_KEY"]
AGENTPHONE_BASE = "https://agentphone.app/api/v1"
AGENTPHONE_HEADERS = {
    "x-api-key": AGENTPHONE_API_KEY,
    "Content-Type": "application/json",
}


@function_tool
def place_phone_call(
    to_phone_number: str,
    objective: str,
    business_name: str = "",
    website: str = "",
) -> str:
    """Place a real phone call to a business or person.

    Use this tool when a workflow step requires calling someone by phone.
    Common uses: booking appointments, verifying information, following up
    on orders, getting quotes, checking availability.

    The call is handled by a voice AI that navigates IVR menus, waits on
    hold, and has a natural conversation. Returns the outcome, a summary,
    and the full transcript.

    Args:
        to_phone_number: Phone number in E.164 format (e.g. +14155551234)
        objective: What the call should accomplish. Be specific — include
                   names, dates, reference numbers, anything relevant.
        business_name: Name of the business being called (helps the AI).
        website: URL of the business website (AI scrapes for context).
    """
    # Create the call
    response = httpx.post(
        f"{AGENTPHONE_BASE}/calls",
        headers=AGENTPHONE_HEADERS,
        json={
            "to_phone_number": to_phone_number,
            "objective": objective,
            "business_name": business_name,
            "website": website,
        },
    )
    call_data = response.json()

    if response.status_code != 202:
        return f"Failed to create call: {call_data}"

    call_id = call_data["data"]["call_id"]

    # Poll until the call finishes
    while True:
        time.sleep(4)
        result = httpx.get(
            f"{AGENTPHONE_BASE}/calls/{call_id}",
            headers=AGENTPHONE_HEADERS,
        ).json()["data"]

        if result["status"] in ("completed", "failed", "canceled"):
            break

    # Return structured result as a string (SDK expects str return)
    return (
        f"Call Status: {result['status']}\n"
        f"Outcome: {result.get('outcome', 'unknown')}\n"
        f"Outcome Details: {result.get('outcome_details', '')}\n"
        f"Summary: {result.get('summary', '')}\n"
        f"Duration: {result.get('duration_seconds', 0)} seconds\n"
        f"Transcript:\n{result.get('transcript', 'No transcript available')}"
    )

Three things to notice:

  1. The docstring is the tool description. The LLM reads it to decide when to use the tool. Be specific about use cases — "booking appointments, verifying information, following up on orders."

  2. The function blocks until the call completes. The SDK handles this — it runs the function and returns the result to the agent when done. A typical call takes 30-120 seconds.

  3. Return type is str. The SDK expects string returns from function tools. We format the result as readable text the agent can parse.

Creating the Agent

Wire the tool into an agent:

from agents import Agent

scheduling_agent = Agent(
    name="scheduling_agent",
    instructions="""You help users schedule appointments and manage bookings.

When a user asks you to book something at a business:
1. If the business has online booking, use that.
2. If the business is phone-only, use the place_phone_call tool.
3. After the call, confirm the details with the user.

When using place_phone_call:
- Always use E.164 format for phone numbers (e.g. +14155551234)
- Be specific in the objective — include dates, times, names, any relevant details
- Include the business_name when you know it
- If the outcome is "not_achieved", tell the user what happened and ask how to proceed
- If the outcome is "partial", report what was accomplished and what remains""",
    tools=[place_phone_call],
)

The instructions field tells the agent how to behave. Note the explicit guidance on how to interpret call outcomes — this prevents the agent from silently swallowing failures.

Running It

import asyncio
from agents import Runner

async def main():
    result = await Runner.run(
        scheduling_agent,
        "Book a dental cleaning at Pacific Heights Dental, their number is "
        "+14155550789. I'd prefer next Thursday morning. My name is Sarah Chen "
        "and I have Delta Dental PPO insurance."
    )
    print(result.final_output)

asyncio.run(main())

The agent parses the request, calls the tool, waits for the result, and reports back.

Multi-Agent Handoff

The OpenAI Agents SDK supports handoffs between specialized agents. You can have one agent that handles research and another that handles phone calls:

from agents import Agent

researcher = Agent(
    name="researcher",
    instructions="You research businesses and gather contact information. "
                 "When the user needs to actually call a business, hand off "
                 "to the caller agent.",
    tools=[web_search],
    handoffs=["caller"],
)

caller = Agent(
    name="caller",
    instructions="You handle phone calls to businesses. Use the research "
                 "from the previous agent to make informed calls. After the "
                 "call, report the results directly to the user.",
    tools=[place_phone_call],
)

# Runner handles handoffs automatically
result = await Runner.run(
    researcher,
    "Find a pet groomer in the Mission that does cat grooming "
    "and book an appointment for this Saturday."
)

The researcher agent finds grooming businesses, then hands off to the caller agent with the phone number and context. The caller makes the call and reports results.

Handling Failures

Phone calls fail. Lines are busy, offices are closed, voicemail picks up. Your agent's instructions should handle these cases:

caller = Agent(
    name="caller",
    instructions="""You handle phone calls for the user.

After each call, check the outcome:
- "achieved": The objective was accomplished. Report the details.
- "not_achieved": The call connected but the objective failed. Tell the user
  why (from outcome_details) and suggest alternatives.
- "partial": Some progress was made. Report what was accomplished and
  what remains.

If the call status is "failed" (didn't connect at all):
- Tell the user the call didn't go through.
- Suggest trying again later or at a different number.

Never retry a call without asking the user first.""",
    tools=[place_phone_call],
)

What You Can Build

  • Appointment scheduling — Book, reschedule, cancel at phone-only businesses
  • Insurance verification — Call providers to check coverage before patient visits
  • Vendor coordination — Follow up on overdue POs, get delivery ETAs, negotiate terms
  • Lead qualification — Call leads to verify contact info and interest before routing to sales
  • Research — Call businesses to gather info not available online (pricing, availability, custom quotes)
  • Service cancellation — Cancel subscriptions, capture confirmation numbers

Getting Started

Get an API key at agentphone.app, copy the tool, add it to your agent. Full API reference at agentphone.app/docs.

For other frameworks: LangChain | CrewAI | MCP

Ready to give your agent a phone?

Get Your API Key →

Written by Yanis Mellata, Founder & CEO