Programmatically Initiate Outbound Calls on Behalf of an Agent
What You Will Build
- A Python script that triggers an outbound phone call from a specified Genesys Cloud user (agent) to an external number.
- This tutorial utilizes the
POST /api/v2/conversations/callsendpoint via the official Genesys Cloud Python SDK (genesyscloud). - The primary language covered is Python, leveraging the
requestslibrary for raw HTTP examples and the SDK for production-ready implementation.
Prerequisites
- OAuth Client Type: Machine-to-Machine (M2M) or JWT flow. The user acting as the caller must have the
calloutcapability. - Required Scopes:
call:outbound:make(Required to initiate the call)user:read(Optional, if you need to verify user details before calling)conversation:write(Implicitly required for managing the conversation lifecycle)
- SDK Version:
genesyscloud>= 1.5.0 - Language/Runtime: Python 3.8+
- Dependencies:
pip install genesyscloud requests
Authentication Setup
Genesys Cloud APIs require OAuth 2.0 authentication. For server-side automation, the Machine-to-Machine (M2M) flow is the standard. You must generate a client ID and client secret from the Genesys Cloud Admin Console under Security > Applications.
The following code demonstrates how to obtain an access token using the requests library. In production, you should implement token caching and refresh logic to avoid re-authenticating on every request.
import requests
import json
def get_access_token(client_id: str, client_secret: str, environment: str = "my.genesis.com") -> str:
"""
Retrieves an OAuth 2.0 access token using M2M flow.
"""
url = f"https://{environment}/oauth/token"
payload = {
"grant_type": "client_credentials"
}
headers = {
"Content-Type": "application/x-www-form-urlencoded"
}
# Basic Auth for Client ID/Secret
auth = (client_id, client_secret)
response = requests.post(url, data=payload, headers=headers, auth=auth)
if response.status_code != 200:
raise Exception(f"Authentication failed: {response.status_code} - {response.text}")
return response.json()["access_token"]
# Example Usage
# token = get_access_token("your_client_id", "your_client_secret")
For the SDK examples below, we will use the genesyscloud client initialization which handles token management internally.
Implementation
Step 1: Initialize the SDK and Configure the Client
The Genesys Cloud Python SDK (genesyscloud) provides a fluent interface for API calls. You must initialize the client with your environment and credentials. The SDK automatically handles OAuth token acquisition and refresh.
from genesyscloud.rest import Configuration
from genesyscloud.api import ConversationApi
from genesyscloud.models import CreateCallConversationRequest
import os
def initialize_api_client(environment: str, client_id: str, client_secret: str) -> ConversationApi:
"""
Initializes the Genesys Cloud Conversation API client.
"""
# Configure the API client with OAuth credentials
config = Configuration(
base_path=f"https://{environment}/api/v2",
client_id=client_id,
client_secret=client_secret
)
# Create the API instance
api_instance = ConversationApi(config=config)
return api_instance
Important: Ensure that the client_id and client_secret have the call:outbound:make scope. If the scope is missing, the API will return a 403 Forbidden error.
Step 2: Construct the Call Payload
To initiate a call, you must provide the to address (the destination number) and the from address (the agent or user initiating the call). The from address must be a valid user or queue ID in your Genesys Cloud organization.
The CreateCallConversationRequest model requires the following fields:
to: The destination phone number in E.164 format (e.g.,+14155551234).from: The Genesys Cloud user ID or queue ID making the call.media_type: Usuallyvoicefor standard calls.
def create_call_request(from_user_id: str, to_number: str) -> CreateCallConversationRequest:
"""
Constructs the payload for initiating an outbound call.
"""
# E.164 format is required for phone numbers
destination = f"tel:{to_number}"
# The 'from' field can be a user ID, queue ID, or other valid addressable entity
source = f"user:{from_user_id}"
payload = CreateCallConversationRequest(
to=destination,
from_=source,
media_type="voice"
)
return payload
Note on E.164 Format: The tel: prefix is mandatory for the to and from fields in Genesys Cloud call APIs. Failure to include this prefix will result in a 400 Bad Request error.
Step 3: Execute the Call and Handle the Response
The POST /api/v2/conversations/calls endpoint is asynchronous in nature. It returns immediately with a conversation object if the request is accepted. It does not wait for the call to connect or complete.
def initiate_outbound_call(api_instance: ConversationApi, from_user_id: str, to_number: str):
"""
Initiates an outbound call and returns the conversation ID.
"""
try:
# Construct the request payload
call_request = create_call_request(from_user_id, to_number)
# Execute the API call
# The api_instance.post_conversation_call method maps to POST /api/v2/conversations/calls
response = api_instance.post_conversation_call(body=call_request)
print(f"Call initiated successfully.")
print(f"Conversation ID: {response.id}")
print(f"Conversation State: {response.state}")
print(f"From: {response.from_}")
print(f"To: {response.to}")
return response.id
except Exception as e:
# The SDK raises an ApiException for HTTP errors
print(f"Failed to initiate call: {e}")
if hasattr(e, 'status'):
print(f"HTTP Status: {e.status}")
if hasattr(e, 'reason'):
print(f"Reason: {e.reason}")
if hasattr(e, 'body'):
print(f"Response Body: {e.body}")
raise
Step 4: Verify the Conversation State (Optional but Recommended)
After initiating the call, you may want to poll the conversation status to confirm it was accepted by the telephony backend. The conversation state will transition from initiated to connected, ringing, or failed.
def get_conversation_details(api_instance: ConversationApi, conversation_id: str):
"""
Retrieves the current status of a conversation.
"""
try:
# GET /api/v2/conversations/{conversationId}
response = api_instance.get_conversation(conversation_id=conversation_id)
print(f"Conversation {conversation_id} is in state: {response.state}")
return response
except Exception as e:
print(f"Error retrieving conversation: {e}")
raise
Complete Working Example
This script combines all steps into a single runnable module. Replace the placeholder credentials with your actual Genesys Cloud application credentials.
import os
import time
from genesyscloud.rest import Configuration
from genesyscloud.api import ConversationApi
from genesyscloud.models import CreateCallConversationRequest
from genesyscloud.rest import ApiException
class GenesysCallInitiator:
def __init__(self, environment: str, client_id: str, client_secret: str):
self.environment = environment
self.client_id = client_id
self.client_secret = client_secret
self.api_instance = self._initialize_api_client()
def _initialize_api_client(self) -> ConversationApi:
"""Initializes the Genesys Cloud Conversation API client."""
config = Configuration(
base_path=f"https://{self.environment}/api/v2",
client_id=self.client_id,
client_secret=self.client_secret
)
return ConversationApi(config=config)
def initiate_call(self, from_user_id: str, to_number: str) -> str:
"""
Initiates an outbound call.
Args:
from_user_id (str): The Genesys Cloud User ID of the agent.
to_number (str): The destination phone number in E.164 format (e.g., +14155551234).
Returns:
str: The Conversation ID.
"""
# Validate E.164 format
if not to_number.startswith('+'):
raise ValueError("Phone number must be in E.164 format starting with '+'")
try:
# Construct the payload
payload = CreateCallConversationRequest(
to=f"tel:{to_number}",
from_=f"user:{from_user_id}",
media_type="voice"
)
# Initiate the call
response = self.api_instance.post_conversation_call(body=payload)
print(f"Call initiated. Conversation ID: {response.id}")
print(f"State: {response.state}")
return response.id
except ApiException as e:
print(f"API Error: {e.status} - {e.reason}")
if e.body:
print(f"Error Details: {e.body}")
raise
except Exception as e:
print(f"Unexpected Error: {e}")
raise
def main():
# Configuration - Replace with your actual credentials
ENVIRONMENT = "my.genesis.com" # e.g., "my.genesis.com", "mypurecloud.com"
CLIENT_ID = os.getenv("GENESYS_CLIENT_ID")
CLIENT_SECRET = os.getenv("GENESYS_CLIENT_SECRET")
if not CLIENT_ID or not CLIENT_SECRET:
raise EnvironmentError("GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET environment variables are required.")
# The User ID of the agent making the call
AGENT_USER_ID = "12345678-1234-1234-1234-123456789012"
# The destination number
DESTINATION_NUMBER = "+14155551234"
# Initialize the client
caller = GenesysCallInitiator(ENVIRONMENT, CLIENT_ID, CLIENT_SECRET)
try:
# Initiate the call
conversation_id = caller.initiate_call(AGENT_USER_ID, DESTINATION_NUMBER)
# Optional: Wait and check status
time.sleep(5)
# Note: In a production app, you would poll the conversation API or use Webhooks
# to track the call lifecycle.
except Exception as e:
print(f"Failed to complete operation: {e}")
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 403 Forbidden
Cause: The OAuth client lacks the call:outbound:make scope, or the user specified in the from field does not have permission to make outbound calls.
Fix:
- Go to the Genesys Cloud Admin Console.
- Navigate to Security > Applications.
- Edit your application.
- Under Scopes, ensure
call:outbound:makeis checked. - Verify that the user ID used in
from_has the “Outbound Call” capability in their user profile.
Error: 400 Bad Request
Cause: Invalid phone number format or missing tel: prefix.
Fix: Ensure the to and from fields follow the format tel:+14155551234. The number must be in E.164 format.
Error: 404 Not Found
Cause: The user ID specified in the from field does not exist in your Genesys Cloud organization.
Fix: Verify the user ID by calling GET /api/v2/users/{userId}.
Error: 429 Too Many Requests
Cause: You have exceeded the rate limit for the API endpoint.
Fix: Implement exponential backoff retry logic. The Genesys Cloud SDK does not automatically retry 429 errors.
import time
def post_with_retry(api_instance, body, max_retries=3):
for attempt in range(max_retries):
try:
return api_instance.post_conversation_call(body=body)
except ApiException as e:
if e.status == 429:
wait_time = 2 ** attempt # Exponential backoff
print(f"Rate limited. Waiting {wait_time} seconds...")
time.sleep(wait_time)
else:
raise
raise Exception("Max retries exceeded")