Initiating Outbound Calls via API: A Developer Guide to POST /api/v2/conversations/calls
What You Will Build
- You will build a script that programmatically initiates an outbound phone call on behalf of a specific Genesys Cloud agent.
- You will use the Genesys Cloud Conversations API (
POST /api/v2/conversations/calls) to create the call leg and connect it to a destination. - You will implement this solution in Python using the
genesyscloudSDK and raw HTTP requests for comparison.
Prerequisites
- OAuth Client Type: Service Account or Confidential Client.
- Required Scopes:
conversations:call:create(Required to initiate the call)conversation:call:monitor(Optional, useful for verifying the call state)users:read(Required if you need to resolve user IDs from emails/names)
- SDK Version:
genesyscloudPython SDK v2.1.0 or later. - Runtime Requirements: Python 3.8+.
- External Dependencies:
genesyscloud(The official SDK)requests(For raw HTTP examples)
Authentication Setup
Genesys Cloud APIs require OAuth 2.0 Bearer tokens. For service-to-service communication, you must use the Client Credentials grant flow. The token expires after 3600 seconds (1 hour). Your application must handle token caching and refresh to avoid unnecessary authentication overhead.
Below is a robust authentication helper using the genesyscloud SDK’s built-in authentication manager. This handles the token lifecycle automatically.
import os
from purecloud_platform_client.configuration import Configuration
from purecloud_platform_client.rest import ApiException
from purecloud_platform_client import PlatformClient
def get_platform_client() -> PlatformClient:
"""
Initializes and returns a configured PlatformClient instance.
Uses environment variables for credentials.
"""
# Load credentials from environment variables
client_id = os.getenv("GENESYS_CLIENT_ID")
client_secret = os.getenv("GENESYS_CLIENT_SECRET")
environment = os.getenv("GENESYS_ENVIRONMENT", "us-east-1") # Default to us-east-1
if not client_id or not client_secret:
raise ValueError("GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET environment variables are required.")
# Create configuration object
config = Configuration(
client_id=client_id,
client_secret=client_secret,
environment=environment
)
# Initialize PlatformClient
# The SDK handles token acquisition and refresh automatically
platform_client = PlatformClient(config)
return platform_client
If you prefer raw HTTP requests, you must manually manage the token. Note that the token endpoint varies by region (e.g., https://api.mypurecloud.com/oauth/token for US East).
import requests
import time
class GenesysAuth:
def __init__(self, client_id: str, client_secret: str, base_url: str):
self.client_id = client_id
self.client_secret = client_secret
self.token_url = f"{base_url}/oauth/token"
self.access_token = None
self.token_expiry = 0
def get_token(self) -> str:
if self.access_token and time.time() < self.token_expiry - 60: # Refresh 1 min before expiry
return self.access_token
payload = {
"grant_type": "client_credentials"
}
headers = {
"Content-Type": "application/x-www-form-urlencoded"
}
response = requests.post(
self.token_url,
data=payload,
headers=headers,
auth=(self.client_id, self.client_secret)
)
response.raise_for_status()
data = response.json()
self.access_token = data["access_token"]
self.token_expiry = time.time() + data["expires_in"]
return self.access_token
Implementation
Step 1: Identify the Agent and Destination
Before initiating the call, you must know the User ID of the agent who will receive the call and the Phone Number to dial. The API requires the from field to be a valid Genesys Cloud user ID. You cannot use an email address directly in the call creation payload.
If you have the user’s email, you must query the Users API first.
def get_user_by_email(platform_client: PlatformClient, email: str) -> str:
"""
Retrieves the User ID for a given email address.
"""
users_api = platform_client.UsersApi()
try:
# Search for user by email
result = users_api.post_users_query(
body={"email": email, "pageSize": 1}
)
if result.entities and len(result.entities) > 0:
return result.entities[0].id
else:
raise ValueError(f"User with email {email} not found.")
except ApiException as e:
if e.status == 404:
raise ValueError(f"User with email {email} not found.")
raise
Step 2: Construct the Call Payload
The core of this tutorial is the POST /api/v2/conversations/calls request. The payload must conform to the CallConversationRequest schema.
Key fields:
from: Object containingid(User ID) andtype(usuallyuser).to: Object containingphoneNumber(E.164 format) andtype(phone_number).wrapUpCode: Optional. If omitted, the system uses the default wrap-up code for the user or queue.notes: Optional. Internal notes attached to the conversation.
Here is the SDK implementation.
from purecloud_platform_client.models import CallConversationRequest
from purecloud_platform_client.models import FromToAddress
from purecloud_platform_client.rest import ApiException
def initiate_outbound_call(platform_client: PlatformClient, agent_user_id: str, destination_number: str) -> str:
"""
Initiates an outbound call.
Args:
platform_client: Authenticated PlatformClient instance.
agent_user_id: The ID of the agent to receive the call.
destination_number: The phone number to dial (E.164 format, e.g., +14155552671).
Returns:
The Conversation ID of the created call.
"""
conversations_api = platform_client.ConversationsApi()
# Construct the 'from' object (The Agent)
from_address = FromToAddress(
id=agent_user_id,
type="user"
)
# Construct the 'to' object (The Destination)
to_address = FromToAddress(
phone_number=destination_number,
type="phone_number"
)
# Construct the main request object
call_request = CallConversationRequest(
from_address=from_address,
to=to_address,
# Optional: Add internal notes
# notes="Automated outbound call via API",
# Optional: Set a specific wrap-up code ID if needed
# wrap_up_code_id="some-wrapup-code-id"
)
try:
# Execute the API call
response = conversations_api.post_conversations_calls(
body=call_request
)
print(f"Call initiated successfully. Conversation ID: {response.id}")
return response.id
except ApiException as e:
# Handle specific API errors
print(f"API Error {e.status}: {e.reason}")
print(f"Response Body: {e.body}")
# Common errors:
# 400: Invalid phone number format or invalid user ID
# 401: Unauthorized (Token expired)
# 403: Forbidden (Insufficient scopes)
# 429: Rate Limit Exceeded
if e.status == 429:
print("Rate limit hit. Wait before retrying.")
elif e.status == 400:
print("Bad Request. Check phone number format (E.164) and User ID.")
raise
Step 3: Handling Rate Limits and Retries
Genesys Cloud enforces strict rate limits. A 429 status code indicates you have exceeded the allowed number of requests per second. A production-grade script must implement exponential backoff.
import time
import random
def post_with_retry(conversations_api, body, max_retries=3):
"""
Wrapper for post_conversations_calls with exponential backoff for 429 errors.
"""
for attempt in range(max_retries):
try:
return conversations_api.post_conversations_calls(body=body)
except ApiException as e:
if e.status == 429:
# Exponential backoff: 1s, 2s, 4s, etc.
wait_time = (2 ** attempt) + random.uniform(0, 1)
print(f"Rate limited. Retrying in {wait_time:.2f} seconds...")
time.sleep(wait_time)
else:
# Non-retryable error
raise
raise Exception("Max retries exceeded due to rate limiting.")
Complete Working Example
This is a complete, runnable Python script. It assumes you have set the environment variables GENESYS_CLIENT_ID, GENESYS_CLIENT_SECRET, and GENESYS_ENVIRONMENT.
import os
import sys
from purecloud_platform_client.configuration import Configuration
from purecloud_platform_client import PlatformClient
from purecloud_platform_client.models import CallConversationRequest, FromToAddress
from purecloud_platform_client.rest import ApiException
def get_platform_client() -> PlatformClient:
client_id = os.getenv("GENESYS_CLIENT_ID")
client_secret = os.getenv("GENESYS_CLIENT_SECRET")
environment = os.getenv("GENESYS_ENVIRONMENT", "us-east-1")
if not client_id or not client_secret:
raise ValueError("GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET environment variables are required.")
config = Configuration(
client_id=client_id,
client_secret=client_secret,
environment=environment
)
return PlatformClient(config)
def initiate_outbound_call(agent_user_id: str, destination_number: str) -> str:
platform_client = get_platform_client()
conversations_api = platform_client.ConversationsApi()
# Validate inputs
if not agent_user_id:
raise ValueError("Agent User ID is required.")
if not destination_number.startswith('+'):
raise ValueError("Destination number must be in E.164 format (start with +).")
# Build the request body
from_address = FromToAddress(
id=agent_user_id,
type="user"
)
to_address = FromToAddress(
phone_number=destination_number,
type="phone_number"
)
call_request = CallConversationRequest(
from_address=from_address,
to=to_address
)
try:
# Initiate the call
response = conversations_api.post_conversations_calls(body=call_request)
print(f"Success! Call initiated.")
print(f"Conversation ID: {response.id}")
print(f"State: {response.state}")
return response.id
except ApiException as e:
print(f"Failed to initiate call. Status Code: {e.status}")
print(f"Reason: {e.reason}")
print(f"Details: {e.body}")
sys.exit(1)
if __name__ == "__main__":
# Example usage
# Replace these with real values from your Genesys Cloud environment
AGENT_USER_ID = "your-agent-user-id-here"
DESTINATION_NUMBER = "+14155552671"
if AGENT_USER_ID == "your-agent-user-id-here":
print("Error: Please update AGENT_USER_ID and DESTINATION_NUMBER in the script.")
sys.exit(1)
initiate_outbound_call(AGENT_USER_ID, DESTINATION_NUMBER)
Common Errors & Debugging
Error: 400 Bad Request - Invalid Phone Number
- Cause: The destination number is not in E.164 format. Genesys Cloud strictly requires international format with a leading plus sign (e.g.,
+14155552671). Local formats like4155552671or(415) 555-2671will fail. - Fix: Ensure the phone number string starts with
+and contains the country code. Use a library likephonenumbersin Python to validate and format numbers before sending them to the API.
Error: 403 Forbidden - Insufficient Scopes
- Cause: The OAuth token does not have the
conversations:call:createscope. - Fix: Update your OAuth Client configuration in the Genesys Cloud Admin Console. Navigate to Organization > Security > OAuth Clients. Edit your client and add
conversations:call:createto the scopes. You must regenerate the client secret if you change scopes on some client types, or simply re-authenticate.
Error: 403 Forbidden - User Not Found or Invalid
- Cause: The
fromuser ID does not exist, or the user is not enabled for outbound calls. - Fix: Verify the
agent_user_idis correct. Ensure the user has an associated phone system user (if using Genesys Cloud Voice) and that the user is active.
Error: 429 Too Many Requests
- Cause: You have exceeded the API rate limit for the
POST /api/v2/conversations/callsendpoint. - Fix: Implement exponential backoff logic as shown in Step 3. Do not retry immediately. Respect the
Retry-Afterheader if present in the response.
Error: 500 Internal Server Error
- Cause: A transient server-side issue.
- Fix: Retry the request after a short delay. If the error persists, contact Genesys Cloud Support with the Conversation ID (if available) or the timestamp of the failure.