Initiate an Outbound Call on Behalf of an Agent via Genesys Cloud API
What You Will Build
- This tutorial demonstrates how to programmatically initiate an outbound call from a Genesys Cloud user (agent) to an external phone number using the REST API.
- The solution utilizes the
POST /api/v2/conversations/callsendpoint to create a new voice conversation. - The implementation is provided in Python using the official
genesys-cloud-purecloud-sdkand in Node.js using the@genesyscloud/genesys-cloud-purecloud-sdk.
Prerequisites
- OAuth Client: A Genesys Cloud application with the
Client Credentialsgrant type. - Required Scopes:
conversation:call:create(Required to initiate the call)user:read(Optional, if you need to look up user details before calling)
- SDK Versions:
- Python:
genesys-cloud-purecloud-sdk>= 2.0.0 - Node.js:
@genesyscloud/genesys-cloud-purecloud-sdk>= 1.0.0
- Python:
- Runtime:
- Python 3.8+
- Node.js 14+
- Dependencies:
- Python:
pip install genesys-cloud-purecloud-sdk - Node.js:
npm install @genesyscloud/genesys-cloud-purecloud-sdk
- Python:
Authentication Setup
Genesys Cloud APIs use OAuth 2.0 for authentication. For server-to-server integrations (like an outbound dialer or backend service), the Client Credentials flow is the standard approach. This flow exchanges your Client ID and Client Secret for an access token valid for one hour.
Python Authentication
import os
from purecloud_platform_client import Configuration, ApiClient
import purecloud_platform_client.rest as rest
def get_purecloud_api_client():
"""
Configures and returns a PureCloud API client instance using Client Credentials flow.
"""
# Load credentials from environment variables
client_id = os.environ.get("GENESYS_CLIENT_ID")
client_secret = os.environ.get("GENESYS_CLIENT_SECRET")
if not client_id or not client_secret:
raise ValueError("GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET environment variables are required.")
# Initialize the configuration
config = Configuration()
config.host = "https://api.mypurecloud.com" # Adjust region if necessary (e.g., api.us.genesyscloud.com)
config.oauth_client_id = client_id
config.oauth_client_secret = client_secret
# Create the API client
api_client = ApiClient(configuration=config)
# Perform the OAuth token exchange
# This automatically handles token storage and refresh within the session
try:
api_client.reauthenticate()
except Exception as e:
raise RuntimeError(f"Failed to authenticate with Genesys Cloud: {e}")
return api_client
Node.js Authentication
import { Configuration, ApiClient } from '@genesyscloud/genesys-cloud-purecloud-sdk';
/**
* Configures and returns a Genesys Cloud API client instance using Client Credentials flow.
*/
export async function getGenesysApiClient() {
const clientId = process.env.GENESYS_CLIENT_ID;
const clientSecret = process.env.GENESYS_CLIENT_SECRET;
if (!clientId || !clientSecret) {
throw new Error('GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET environment variables are required.');
}
const config = new Configuration({
basePath: 'https://api.mypurecloud.com', // Adjust region if necessary
clientId: clientId,
clientSecret: clientSecret
});
const apiClient = new ApiClient(config);
try {
// Perform the OAuth token exchange
await apiClient.reauthenticate();
} catch (error) {
throw new Error(`Failed to authenticate with Genesys Cloud: ${error.message}`);
}
return apiClient;
}
Implementation
Step 1: Construct the Outbound Call Request
To initiate a call, you must send a POST request to /api/v2/conversations/calls. The request body must contain a to object representing the recipient and optionally a from object representing the caller identity.
Critical Parameter: from
The from field determines who is making the call. It can be:
- A User: Identified by the user’s Genesys Cloud ID or email. This routes the call through that user’s configured DID (Direct Inbound Dialing) number.
- A Station: Identified by the station ID. This uses the specific phone line associated with that station.
- An Outbound Route: If you have configured outbound routes, you can specify the route ID to ensure the call leaves via the correct carrier.
For this tutorial, we will use a User as the caller identity. This is the most common pattern for “on behalf of” scenarios.
Python Implementation
from purecloud_platform_client import (
CallsApi,
CallConversationRequest,
ConversationParticipantRequest,
ConversationParticipantRequestFrom,
ConversationParticipantRequestTo
)
def initiate_outbound_call(api_client, caller_user_id, recipient_phone_number):
"""
Initiates an outbound call from a specific user to a phone number.
Args:
api_client: The authenticated PureCloud API client.
caller_user_id (str): The Genesys Cloud User ID of the agent making the call.
recipient_phone_number (str): The E.164 formatted phone number to call (e.g., "+15551234567").
"""
calls_api = CallsApi(api_client)
# 1. Define the 'from' participant (The Agent)
# Using user_id ensures the call originates from the user's assigned DID.
from_participant = ConversationParticipantRequestFrom(
user_id=caller_user_id
)
# 2. Define the 'to' participant (The Recipient)
# The phone number MUST be in E.164 format for reliable routing.
to_participant = ConversationParticipantRequestTo(
phone_number=recipient_phone_number
)
# 3. Construct the Call Conversation Request
call_request = CallConversationRequest(
from_=from_participant,
to=to_participant
)
try:
# 4. Execute the API call
response = calls_api.post_conversations_calls(body=call_request)
# The response contains the conversation ID
conversation_id = response.conversation_id
print(f"Call initiated successfully. Conversation ID: {conversation_id}")
return conversation_id
except Exception as e:
# Handle API errors
if hasattr(e, 'status') and e.status == 400:
print(f"Bad Request: Check phone number format or user permissions. Details: {e.body}")
elif hasattr(e, 'status') and e.status == 403:
print(f"Forbidden: User lacks permissions or is not in a valid state (e.g., offline). Details: {e.body}")
else:
print(f"Error initiating call: {e}")
raise
Node.js Implementation
import { CallsApi, CallConversationRequest, ConversationParticipantRequestFrom, ConversationParticipantRequestTo } from '@genesyscloud/genesys-cloud-purecloud-sdk';
/**
* Initiates an outbound call from a specific user to a phone number.
*
* @param {ApiClient} apiClient - The authenticated Genesys Cloud API client.
* @param {string} callerUserId - The Genesys Cloud User ID of the agent making the call.
* @param {string} recipientPhoneNumber - The E.164 formatted phone number to call (e.g., "+15551234567").
* @returns {Promise<string>} The Conversation ID.
*/
export async function initiateOutboundCall(apiClient, callerUserId, recipientPhoneNumber) {
const callsApi = new CallsApi(apiClient);
// 1. Define the 'from' participant (The Agent)
// Using userId ensures the call originates from the user's assigned DID.
const fromParticipant = new ConversationParticipantRequestFrom({
userId: callerUserId
});
// 2. Define the 'to' participant (The Recipient)
// The phone number MUST be in E.164 format for reliable routing.
const toParticipant = new ConversationParticipantRequestTo({
phoneNumber: recipientPhoneNumber
});
// 3. Construct the Call Conversation Request
const callRequest = new CallConversationRequest({
from: fromParticipant,
to: toParticipant
});
try {
// 4. Execute the API call
const response = await callsApi.postConversationsCalls({ body: callRequest });
const conversationId = response.body.conversationId;
console.log(`Call initiated successfully. Conversation ID: ${conversationId}`);
return conversationId;
} catch (error) {
// Handle API errors
if (error.status === 400) {
console.error(`Bad Request: Check phone number format or user permissions. Details: ${JSON.stringify(error.body)}`);
} else if (error.status === 403) {
console.error(`Forbidden: User lacks permissions or is not in a valid state (e.g., offline). Details: ${JSON.stringify(error.body)}`);
} else {
console.error(`Error initiating call:`, error.message);
}
throw error;
}
}
Step 2: Understanding the Response and Edge Cases
The POST /api/v2/conversations/calls endpoint returns a 201 Created status code upon successful initiation. The response body is a CallConversationResponse object.
Key Response Fields:
conversationId: The unique identifier for this call session. You will need this ID to monitor the call status, transfer the call, or hang up programmatically.participants: An array containing the initial participants. Note that thefromparticipant (the agent) is automatically added to this list.
Edge Case: User State
If the user specified in the from field is Offline or Not Available, the call will typically fail with a 400 Bad Request or 403 Forbidden error, depending on your organization’s routing policies. To mitigate this, you should check the user’s presence before initiating the call.
Checking User Presence (Python)
from purecloud_platform_client import UsersApi
def check_user_presence(api_client, user_id):
"""
Checks if the user is currently available to make calls.
"""
users_api = UsersApi(api_client)
try:
user_response = users_api.get_user(user_id=user_id)
# Presence is not directly in the User object, but we can infer from the last updated time or use the Presence API
# However, a simpler check is to see if the user has a station configured and is licensed for voice.
if not user_response.licensed_features or 'voice' not in [f.name for f in user_response.licensed_features]:
return False, "User does not have a voice license."
# A more robust check involves the Presence API, but for simplicity, we assume valid configuration.
return True, "User is valid."
except Exception as e:
return False, f"Error checking user: {e}"
Step 3: Monitoring Call Status
Once the call is initiated, it enters a queued or connecting state. To monitor the progress, you can use the GET /api/v2/conversations/calls/{conversationId} endpoint.
Python Monitoring Example
import time
def monitor_call_status(api_client, conversation_id, timeout_seconds=60):
"""
Polls the call status until it connects, fails, or times out.
"""
calls_api = CallsApi(api_client)
start_time = time.time()
while time.time() - start_time < timeout_seconds:
try:
call_response = calls_api.get_conversations_call(conversation_id=conversation_id)
# Check the state of the call
# The 'participants' list contains the current state of each leg
if call_response.participants:
# Typically, the first participant is the caller, second is the callee
# We look for the 'to' participant's state
for participant in call_response.participants:
if participant.to and participant.to.phone_number:
print(f"Call State: {participant.state}")
if participant.state in ['connected', 'disconnected', 'failed', 'busy']:
return participant.state
time.sleep(2) # Wait 2 seconds before polling again
except Exception as e:
print(f"Error polling call status: {e}")
return "error"
return "timeout"
Complete Working Example
Below is a complete, runnable Python script that authenticates, checks user validity, initiates the call, and monitors its initial status.
import os
import sys
import time
from purecloud_platform_client import (
Configuration,
ApiClient,
CallsApi,
UsersApi,
CallConversationRequest,
ConversationParticipantRequestFrom,
ConversationParticipantRequestTo
)
def main():
# 1. Setup Authentication
client_id = os.environ.get("GENESYS_CLIENT_ID")
client_secret = os.environ.get("GENESYS_CLIENT_SECRET")
if not client_id or not client_secret:
print("Error: GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET environment variables are required.")
sys.exit(1)
config = Configuration()
config.host = "https://api.mypurecloud.com"
config.oauth_client_id = client_id
config.oauth_client_secret = client_secret
api_client = ApiClient(configuration=config)
try:
api_client.reauthenticate()
except Exception as e:
print(f"Authentication failed: {e}")
sys.exit(1)
# 2. Define Call Parameters
caller_user_id = "YOUR_USER_ID_HERE" # Replace with actual User ID
recipient_phone_number = "+15551234567" # Replace with E.164 number
if caller_user_id == "YOUR_USER_ID_HERE":
print("Error: Please replace YOUR_USER_ID_HERE with a valid Genesys Cloud User ID.")
sys.exit(1)
# 3. Validate User (Optional but Recommended)
users_api = UsersApi(api_client)
try:
user_response = users_api.get_user(user_id=caller_user_id)
print(f"Initiating call on behalf of: {user_response.name}")
except Exception as e:
print(f"User validation failed: {e}")
sys.exit(1)
# 4. Initiate Call
calls_api = CallsApi(api_client)
from_participant = ConversationParticipantRequestFrom(user_id=caller_user_id)
to_participant = ConversationParticipantRequestTo(phone_number=recipient_phone_number)
call_request = CallConversationRequest(from_=from_participant, to=to_participant)
try:
response = calls_api.post_conversations_calls(body=call_request)
conversation_id = response.conversation_id
print(f"Call initiated successfully. Conversation ID: {conversation_id}")
# 5. Monitor Initial Status
print("Monitoring call status...")
time.sleep(5) # Give it a few seconds to process
try:
call_status = calls_api.get_conversations_call(conversation_id=conversation_id)
if call_status.participants:
for p in call_status.participants:
print(f"Participant: {p.from_} -> {p.to}, State: {p.state}")
except Exception as e:
print(f"Error fetching status: {e}")
except Exception as e:
if hasattr(e, 'status'):
print(f"API Error {e.status}: {e.body}")
else:
print(f"Error: {e}")
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 400 Bad Request - “Invalid phone number format”
- Cause: The
phone_numberfield in thetoobject is not in E.164 format. - Fix: Ensure the phone number starts with a
+followed by the country code and number (e.g.,+14155552671). Do not include spaces, dashes, or parentheses.
Error: 403 Forbidden - “User is not in a valid state”
- Cause: The user specified in the
fromfield is currently Offline, Lunch, or Break. Genesys Cloud does not allow outbound calls to originate from users who are not in an Available or Working state. - Fix: Use the Presence API to check the user’s status before initiating the call. Alternatively, use a Station ID or Outbound Route ID in the
fromobject instead of auser_idto bypass user presence requirements.
Error: 403 Forbidden - “Insufficient permissions”
- Cause: The OAuth token does not have the
conversation:call:createscope. - Fix: Update your Genesys Cloud application settings to include the
conversation:call:createscope and re-authenticate.
Error: 429 Too Many Requests
- Cause: You have exceeded the rate limit for the
POST /api/v2/conversations/callsendpoint. - Fix: Implement exponential backoff retry logic. The response header
Retry-Afterindicates the number of seconds to wait before retrying.