Skip to navigation

Outbound Calls

Make phone calls programmatically with your AI agent.
View as Markdown

Use start_outbound_call() to dial any phone number and connect it to your agent. This is the production outbound surface - full variables support for per-call personalization, optional caller-ID rotation, and version pinning.

Not to be confused with /agent/{id}/versions/{versionId}/test-call. That endpoint is a debug surface bound to a specific agent draft or version. It only accepts mode and toPhone, doesn’t carry per-call variables, and counts against test-call slot limits. For real outbound traffic - campaigns, lead callbacks, anything customer-facing - use the pattern on this page (which maps to POST /conversation/outbound).

Basic Call

from smallestai import SmallestAI
# Reads SMALLEST_API_KEY from the environment, or pass api_key="sk_..." explicitly
client = SmallestAI()
response = client.atoms.calls.start_outbound_call(
agent_id="your-agent-id",
phone_number="+14155551234",
from_number="+14155559876",
)
print(f"Call started: {response.data.conversation_id}")

Pass from_number (E.164, exact match to a number your org owns) unless the agent already has a caller ID attached. If neither is set, the API returns 400 and does not dial.

Get your API key at app.smallest.ai/dashboard/api-keys and export it as SMALLEST_API_KEY, or pass SmallestAI(api_key="sk_...") directly.

Parameters

These are the parameters the Python SDK’s start_outbound_call() method accepts today.

ParameterTypeRequiredDescription
agent_idstringYesID of your agent (from the dashboard).
phone_numberstringYesE.164 format phone number (e.g. +14155551234, +919876543210).
variablesobjectNoPer-call values that override the agent’s defaultVariables for this conversation. Keys are variable names; values must be string, number, or boolean. Nested objects are not supported. See Personalization below.
from_numberstringNo (required after the migration window)E.164 caller ID to dial from. Matched exactly against numbers your organization owns (rented, or a caller ID on an outbound SIP trunk); no normalization is applied. If omitted, the call uses the deprecated caller-ID bridge (the agent’s attached caller IDs, first attached wins) and the response carries Deprecation: true; with none attached the call is refused with 400. There is no fallback to a platform number.
from_product_idstringNoLegacy. ID of the phone-number product to dial from. Resolved to its number before dialing. Prefer from_number for new integrations.

Campaigns take a different field: fromNumbers on POST /campaign (several caller IDs rotate across audience members, retries reuse the number the failed attempt used, and the list freezes at creation). It is not a /conversation/outbound parameter. See the Telephony API migration guide.

REST-only parameters

The underlying POST /conversation/outbound endpoint accepts two additional parameters that are not yet exposed by the Python SDK. To use them today, call the REST API directly with requests or httpx:

ParameterTypeDescription
versionIdstringPin the call to a specific published agent version. The call log is attributed to that version so you can A/B-test versions in production.
prioritystringnormal (default) or urgent. An urgent call goes ahead of your other queued calls. See Urgent calls.
import os, requests
response = requests.post(
"https://api.smallest.ai/atoms/v1/conversation/outbound",
headers={"Authorization": f"Bearer {os.environ['SMALLEST_API_KEY']}"},
json={
"agentId": "your-agent-id",
"phoneNumber": "+14155551234",
"fromNumber": "+14155559876",
"variables": {"customer_name": "Sarah Johnson", "package": "Gold"},
"versionId": "your-version-id",
},
)
response.raise_for_status()
print("Call started:", response.json()["data"]["conversationId"])

Urgent calls

When your organization has calls waiting for a free concurrency slot, they’re placed in the order they were created. Pass "priority": "urgent" to put one call at the front of that queue, for example a callback a customer just asked for while a campaign is running.

import os, requests
response = requests.post(
"https://api.smallest.ai/atoms/v1/conversation/outbound",
headers={"Authorization": f"Bearer {os.environ['SMALLEST_API_KEY']}"},
json={
"agentId": "your-agent-id",
"phoneNumber": "+14155551234",
"fromNumber": "+14155559876",
"priority": "urgent",
},
)
response.raise_for_status()

A normal call joins the back of the queue. An urgent call joins the front and starts on the next free concurrency slot.

  • It changes queue position only. An urgent call starts as soon as one of your concurrency slots is free. If every slot is busy, it waits for the next call to end, then goes first. It does not raise your concurrency limit or skip any other check.
  • It only reorders your own calls. Each organization has its own queue, so urgent calls never affect another organization.
  • Urgent calls are ordered among themselves by creation time. If every call is marked urgent, they’re placed in the order they were created, the same as if none were.
  • Set it when you create the call. A call that is already queued can’t be made urgent. Campaign calls don’t accept it.
  • Only normal and urgent are accepted. Any other value returns 400 ("priority must be one of: normal, urgent"). Omitting the field is the same as normal.

Personalization (passing per-call variables)

Pass variables to carry caller-specific values into the conversation - names, lead source, amounts, anything the agent should reference.

response = client.atoms.calls.start_outbound_call(
agent_id="your-agent-id",
phone_number="+14155551234",
variables={
"customer_name": "Sarah Johnson",
"package": "Gold",
"lead_source": "facebook_ad",
"vip": True,
},
)
  • Scalar values only. string, number, boolean. Nested objects are rejected with HTTP 400 ("Invalid input") - flatten the payload client-side (address_city instead of address: { city: ... }).
  • Variables are persisted with the call. They appear on the conversation log at data.variables and can be retrieved via GET /conversation/{id}.

See Variables for the agent-side guide to defining and referencing variables.

Response

# response.data.conversation_id = "CALL-1767900635803-8a3bee"
FieldTypeDescription
conversation_idstringUnique ID to track this call

Save this ID to retrieve transcripts and analytics later.

Error Handling

try:
response = client.atoms.calls.start_outbound_call(
agent_id="agent-123",
phone_number="+14155551234",
from_number="+14155559876",
)
print(f"Call started: {response.data.conversation_id}")
except Exception as e:
error = str(e)
if "Invalid agent id" in error:
print("Error: Agent not found. Check your agent ID.")
elif "Invalid phone" in error:
print("Error: Invalid phone number. Use E.164 format.")
elif "No caller ID for this call" in error:
print("Error: Pass from_number, or attach a caller ID to the agent.")
elif "priority must be one of" in error:
print("Error: priority must be normal or urgent.")
elif "401" in error:
print("Error: Check your API key.")
else:
print(f"Error: {error}")

Tips

Use a library like phonenumbers to validate before calling. Invalid numbers waste API calls.

Wait 5-10 seconds between calls to avoid rate limits.

Save them in your database to retrieve transcripts later.

Wrap calls in try/except and log failures for retry.