Initiating an Outbound Call on Behalf of an Agent Using POST /api/v2/conversations/calls
What You Will Build
- You will write a script that programmatically initiates an outbound voice call from a Genesys Cloud user to an external telephone number.
- This uses the Genesys Cloud Platform API v2 endpoint
POST /api/v2/conversations/calls. - The tutorial covers Python, JavaScript, and C# implementations.
Prerequisites
- OAuth Client Type: A Service Account or Machine-to-Machine (M2M) client with sufficient permissions.
- Required OAuth Scopes:
conversation:call:write(to initiate the call)conversation:call:read(to verify the call state)user:read(optional, to verify the agent user ID)
- SDK Version:
- Python:
genesyscloud>= 13.0.0 - JavaScript:
@genesyscloud/genesyscloud>= 13.0.0 - C#:
GenesysCloud>= 13.0.0
- Python:
- Runtime Requirements:
- Python 3.8+
- Node.js 18+
- .NET 6+
- External Dependencies:
- Python:
pip install genesyscloud - Node:
npm install @genesyscloud/genesyscloud - C#:
Install-Package GenesysCloud
- Python:
Authentication Setup
Genesys Cloud uses OAuth 2.0 for authentication. For server-to-server integrations, the Client Credentials flow is standard. You must configure your OAuth client in the Genesys Cloud Admin Console under Setup > Integration > OAuth clients.
Below is the authentication logic for Python. The same pattern applies to other languages using their respective SDKs or HTTP libraries.
Python Authentication Example
import os
from genesyscloud.auth import OAuthClient
from genesyscloud.rest import ApiException
def get_authenticated_oauth_client() -> OAuthClient:
"""
Initializes and authenticates the OAuth client using M2M credentials.
"""
client_id = os.environ.get("GENESYS_CLOUD_CLIENT_ID")
client_secret = os.environ.get("GENESYS_CLOUD_CLIENT_SECRET")
base_url = os.environ.get("GENESYS_CLOUD_BASE_URL", "https://api.mypurecloud.com")
if not client_id or not client_secret:
raise ValueError("GENESYS_CLOUD_CLIENT_ID and GENESYS_CLOUD_CLIENT_SECRET must be set.")
oauth_client = OAuthClient(
client_id=client_id,
client_secret=client_secret,
base_url=base_url
)
try:
oauth_client.authenticate()
print("Authentication successful.")
return oauth_client
except ApiException as e:
print(f"Authentication failed: {e.status} {e.reason}")
raise
# Usage
# oauth_client = get_authenticated_oauth_client()
Implementation
Step 1: Identify the Agent and Target
Before initiating the call, you must identify the User ID of the agent who will handle the call. This user must be logged into the Genesys Cloud desktop application and have an active presence (e.g., “Available” or “Busy”) that allows inbound or outbound interactions, depending on your routing configuration.
You also need the To number (the external party) and the From number (the Genesys Cloud DID assigned to the user or team).
Python: Fetching User Details
If you only know the agent’s name, you can search for them. However, for production code, always cache the User ID.
from genesyscloud.users import UsersApi
def find_user_by_name(oauth_client: OAuthClient, user_name: str) -> str:
"""
Finds a user ID by their name.
Returns the first match found.
"""
users_api = UsersApi(oauth_client=oauth_client)
try:
# Search for users with the given name
response = users_api.post_users_search(
body={
"query": user_name,
"pageSize": 1
}
)
if response.entities and len(response.entities) > 0:
user_id = response.entities[0].id
print(f"Found User ID: {user_id} for name: {user_name}")
return user_id
else:
raise ValueError(f"No user found with name: {user_name}")
except ApiException as e:
print(f"Error searching for user: {e.status} {e.reason}")
raise
# Usage
# agent_id = find_user_by_name(oauth_client, "John Doe")
Step 2: Constructing the Call Request Body
The core of this tutorial is the request body sent to POST /api/v2/conversations/calls. The structure differs slightly from a standard POST /api/v2/conversations call because it is specifically for voice.
Key fields:
to: The recipient’s phone number. Must be in E.164 format (e.g.,+15551234567).from: The caller ID. This must be a DID provisioned in your Genesys Cloud environment and associated with the user or a team.userId: The ID of the agent who will receive the call.type: Set to"voice".
Python: Building the Request
from genesyscloud.conversations import ConversationsApi
from genesyscloud.conversations.models import ConversationCall
def prepare_call_request(agent_id: str, to_number: str, from_number: str) -> ConversationCall:
"""
Constructs the ConversationCall object for the API request.
"""
call_request = ConversationCall(
to=to_number,
from_=from_number, # Note: 'from' is a reserved keyword in Python, so SDK uses from_
user_id=agent_id,
type="voice"
)
return call_request
Step 3: Initiating the Call
You will use the ConversationsApi to send the request. The API returns a 201 Created status with the Conversation ID and the Participant ID for the agent.
Python: Executing the Call
def initiate_outbound_call(oauth_client: OAuthClient, agent_id: str, to_number: str, from_number: str) -> str:
"""
Initiates an outbound call and returns the Conversation ID.
"""
conversations_api = ConversationsApi(oauth_client=oauth_client)
call_request = prepare_call_request(agent_id, to_number, from_number)
try:
# The API call
response = conversations_api.post_conversations_calls(
body=call_request
)
conversation_id = response.id
print(f"Call initiated successfully. Conversation ID: {conversation_id}")
# Optional: Return participant ID for further actions (e.g., hangup)
# The response contains a 'participants' list.
if response.participants:
agent_participant_id = response.participants[0].id
print(f"Agent Participant ID: {agent_participant_id}")
return conversation_id
except ApiException as e:
print(f"Failed to initiate call: {e.status} {e.reason}")
# Common errors:
# 400: Invalid phone number format or invalid DID
# 403: User does not have permission or is not logged in
# 429: Rate limit exceeded
raise
# Usage
# conversation_id = initiate_outbound_call(oauth_client, agent_id, "+15550000000", "+15559999999")
JavaScript Implementation
For Node.js environments, the async/await pattern simplifies error handling.
const { GenesysCloud } = require('@genesyscloud/genesyscloud');
const { ConversationsApi, ConversationCall } = require('@genesyscloud/genesyscloud/models');
async function initiateCallJS() {
const genesys = new GenesysCloud({
clientId: process.env.GENESYS_CLOUD_CLIENT_ID,
clientSecret: process.env.GENESYS_CLOUD_CLIENT_SECRET,
baseUri: process.env.GENESYS_CLOUD_BASE_URL || 'https://api.mypurecloud.com'
});
await genesys.authenticate();
const conversationsApi = new ConversationsApi({
authentication: genesys.authentication,
baseUri: genesys.baseUri
});
const callBody = new ConversationCall({
to: '+15550000000', // Recipient
from: '+15559999999', // Caller ID (DID)
userId: 'AGENT_USER_ID', // Replace with actual User ID
type: 'voice'
});
try {
const response = await conversationsApi.postConversationsCalls({
body: callBody
});
console.log(`Call initiated. Conversation ID: ${response.id}`);
return response.id;
} catch (error) {
console.error(`Error initiating call: ${error.status} ${error.message}`);
throw error;
}
}
// initiateCallJS();
C# Implementation
In .NET, the SDK uses synchronous and asynchronous methods. We use the async version for better performance.
using System;
using System.Threading.Tasks;
using GenesysCloud.Auth;
using GenesysCloud.Conversations;
using GenesysCloud.Models;
public class GenesysCaller
{
private readonly IConversationsApi _conversationsApi;
public GenesysCaller(IConversationsApi conversationsApi)
{
_conversationsApi = conversationsApi;
}
public async Task<string> InitiateCallAsync(string agentId, string toNumber, string fromNumber)
{
var callRequest = new ConversationCall
{
To = toNumber,
From = fromNumber,
UserId = agentId,
Type = "voice"
};
try
{
var response = await _conversationsApi.PostConversationsCallsAsync(body: callRequest);
Console.WriteLine($"Call initiated. Conversation ID: {response.Id}");
return response.Id;
}
catch (ApiException ex)
{
Console.WriteLine($"Error: {ex.ErrorCode} {ex.Message}");
throw;
}
}
}
// Usage Example:
// var auth = new OAuthClient(clientId, clientSecret, baseUrl);
// auth.Authenticate();
// var convApi = new ConversationsApi(auth);
// var caller = new GenesysCaller(convApi);
// await caller.InitiateCallAsync("AGENT_ID", "+15550000000", "+15559999999");
Complete Working Example
This Python script combines authentication, user lookup (optional, assuming ID is known for simplicity), and call initiation. It includes robust error handling and logging.
import os
import sys
import logging
from genesyscloud.auth import OAuthClient
from genesyscloud.conversations import ConversationsApi
from genesyscloud.conversations.models import ConversationCall
from genesyscloud.rest import ApiException
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
def get_oauth_client() -> OAuthClient:
client_id = os.environ.get("GENESYS_CLOUD_CLIENT_ID")
client_secret = os.environ.get("GENESYS_CLOUD_CLIENT_SECRET")
base_url = os.environ.get("GENESYS_CLOUD_BASE_URL", "https://api.mypurecloud.com")
if not client_id or not client_secret:
raise ValueError("Environment variables GENESYS_CLOUD_CLIENT_ID and GENESYS_CLOUD_CLIENT_SECRET are required.")
oauth_client = OAuthClient(client_id=client_id, client_secret=client_secret, base_url=base_url)
try:
oauth_client.authenticate()
return oauth_client
except ApiException as e:
logger.error(f"Authentication failed: {e.status} {e.reason}")
sys.exit(1)
def make_outbound_call(oauth_client: OAuthClient, agent_id: str, to_number: str, from_number: str) -> str:
"""
Initiates an outbound call.
Args:
oauth_client: Authenticated OAuth client.
agent_id: The Genesys Cloud User ID of the agent.
to_number: The recipient's phone number in E.164 format.
from_number: The caller ID (DID) in E.164 format.
Returns:
The Conversation ID.
"""
conversations_api = ConversationsApi(oauth_client=oauth_client)
# Construct the request body
call_request = ConversationCall(
to=to_number,
from_=from_number,
user_id=agent_id,
type="voice"
)
try:
logger.info(f"Initiating call from {from_number} to {to_number} for agent {agent_id}")
response = conversations_api.post_conversations_calls(body=call_request)
conversation_id = response.id
logger.info(f"Call initiated successfully. Conversation ID: {conversation_id}")
# Extract participant ID for potential future actions (e.g., hangup)
if response.participants:
agent_participant_id = response.participants[0].id
logger.info(f"Agent Participant ID: {agent_participant_id}")
return conversation_id
except ApiException as e:
logger.error(f"API Error {e.status}: {e.reason}")
if e.status == 400:
logger.error("Bad Request. Check phone number formats (E.164) and DID validity.")
elif e.status == 403:
logger.error("Forbidden. Ensure the agent is logged in and has permission to make calls.")
elif e.status == 404:
logger.error("Not Found. The User ID or DID may be invalid.")
elif e.status == 429:
logger.error("Rate Limit Exceeded. Implement exponential backoff.")
raise
def main():
# Configuration
AGENT_ID = os.environ.get("AGENT_USER_ID", "DEFAULT_AGENT_ID") # Replace with actual ID
TO_NUMBER = os.environ.get("TO_NUMBER", "+15550000000")
FROM_NUMBER = os.environ.get("FROM_NUMBER", "+15559999999")
if not AGENT_ID:
logger.error("AGENT_USER_ID environment variable is required.")
sys.exit(1)
try:
oauth_client = get_oauth_client()
conversation_id = make_outbound_call(oauth_client, AGENT_ID, TO_NUMBER, FROM_NUMBER)
logger.info(f"Process complete. Conversation ID: {conversation_id}")
except Exception as e:
logger.error(f"Fatal error: {e}")
sys.exit(1)
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 400 Bad Request
Cause: The most common cause is an invalid phone number format. Genesys Cloud requires E.164 format (e.g., +15551234567). Leading zeros, parentheses, or dashes often cause validation failures. Another cause is an invalid from number (DID) that is not provisioned in your Genesys Cloud organization.
Fix:
- Validate phone numbers using a library like
phonenumbersin Python. - Verify the
fromnumber exists in Setup > Calling > Phone numbers. - Ensure the
fromnumber is associated with the agent or a team the agent belongs to.
# Python validation example
import phonenumbers
def validate_e164(phone_number: str) -> bool:
try:
parsed_number = phonenumbers.parse(phone_number, None)
return phonenumbers.is_valid_number(parsed_number)
except phonenumbers.NumberParseException:
return False
Error: 403 Forbidden
Cause: The agent specified in userId is not currently logged into the Genesys Cloud Desktop application, or their presence is set to a state that does not allow outbound calls (e.g., “Offline”). Alternatively, the OAuth client lacks the conversation:call:write scope.
Fix:
- Ensure the agent is logged in to Genesys Cloud Desktop.
- Check the agent’s presence state. They should be “Available” or “Busy” (depending on org settings).
- Verify the OAuth client scopes in the Admin Console.
Error: 429 Too Many Requests
Cause: You have exceeded the API rate limits. Genesys Cloud enforces rate limits per client ID and per endpoint.
Fix: Implement exponential backoff and retry logic.
import time
def make_call_with_retry(oauth_client, agent_id, to_number, from_number, max_retries=3):
for attempt in range(max_retries):
try:
return make_outbound_call(oauth_client, agent_id, to_number, from_number)
except ApiException as e:
if e.status == 429:
wait_time = 2 ** attempt
logger.warning(f"Rate limit hit. Retrying in {wait_time} seconds...")
time.sleep(wait_time)
else:
raise
raise Exception("Max retries exceeded for rate limiting.")
Error: 404 Not Found
Cause: The userId provided does not exist in your Genesys Cloud organization, or the from number (DID) is not found.
Fix: Double-check the User ID and DID values. Use the Admin Console to verify the exact IDs.