Triggering NICE CXone Personal Connection Calls via the API
What You Will Build
- One sentence: You will build a script that programmatically initiates an outbound voice call to a specific contact using the NICE CXone Personal Connection API.
- One sentence: This tutorial uses the CXone REST API endpoints for Personal Connection and standard HTTP libraries.
- One sentence: The implementation is provided in Python using
requestsand JavaScript usingfetch.
Prerequisites
- OAuth Client Type: You need a valid CXone Organization ID and an OAuth Client ID/Secret with the
contactcenterscope. - API Version: The Personal Connection API is accessed via the standard CXone v1 endpoints.
- Language/Runtime: Python 3.8+ or Node.js 16+.
- External Dependencies:
- Python:
pip install requests - Node.js: No external dependencies required (uses built-in
fetchin modern Node versions, ornode-fetchfor older versions).
- Python:
- Personal Connection Setup: You must have a Personal Connection account configured in your CXone organization with a valid phone number assigned to the user or the connection profile.
Authentication Setup
CXone uses OAuth 2.0 for authentication. You must obtain a bearer token before making any API calls. The token expires after a set duration (typically 3600 seconds), so your production code should implement caching and refresh logic. For this tutorial, we will fetch a fresh token each time to ensure clarity.
Python Authentication
import requests
import json
def get_cxone_token(org_id: str, client_id: str, client_secret: str) -> str:
"""
Retrieves an OAuth2 bearer token from CXone.
"""
url = f"https://{org_id}.mypurecloud.com/api/v2/oauth/token"
# CXone uses client_credentials grant for server-to-server API calls
payload = {
"grant_type": "client_credentials",
"client_id": client_id,
"client_secret": client_secret
}
headers = {
"Content-Type": "application/json"
}
try:
response = requests.post(url, json=payload, headers=headers)
response.raise_for_status()
token_data = response.json()
return token_data["access_token"]
except requests.exceptions.HTTPError as e:
print(f"Authentication failed: {e.response.status_code} - {e.response.text}")
raise
except Exception as e:
print(f"An error occurred during authentication: {e}")
raise
# Usage
ORG_ID = "your-org-id"
CLIENT_ID = "your-client-id"
CLIENT_SECRET = "your-client-secret"
try:
ACCESS_TOKEN = get_cxone_token(ORG_ID, CLIENT_ID, CLIENT_SECRET)
print("Token acquired successfully.")
except Exception:
print("Failed to acquire token. Aborting.")
exit(1)
JavaScript Authentication
async function getCxoneToken(orgId, clientId, clientSecret) {
const url = `https://${orgId}.mypurecloud.com/api/v2/oauth/token`;
const payload = {
grant_type: 'client_credentials',
client_id: clientId,
client_secret: clientSecret
};
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload)
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Authentication failed: ${response.status} - ${errorText}`);
}
const data = await response.json();
return data.access_token;
} catch (error) {
console.error("Authentication error:", error);
throw error;
}
}
// Usage
const ORG_ID = "your-org-id";
const CLIENT_ID = "your-client-id";
const CLIENT_SECRET = "your-client-secret";
(async () => {
try {
const accessToken = await getCxoneToken(ORG_ID, CLIENT_ID, CLIENT_SECRET);
console.log("Token acquired successfully.");
// Proceed with API calls...
} catch (error) {
console.error("Failed to acquire token. Aborting.");
process.exit(1);
}
})();
Implementation
Step 1: Identify the Personal Connection Account
Before triggering a call, you must know the personalConnectionAccountId. This ID links the API call to the specific Personal Connection profile (phone number) configured in your CXone organization.
You can retrieve the list of available Personal Connection accounts using the GET /api/v2/contactcenter/personalconnections endpoint.
Required Scope: personalconnection:read
Python: List Accounts
import requests
def list_personal_connections(org_id: str, access_token: str) -> list:
"""
Retrieves all Personal Connection accounts for the organization.
"""
url = f"https://{org_id}.mypurecloud.com/api/v2/contactcenter/personalconnections"
headers = {
"Authorization": f"Bearer {access_token}",
"Accept": "application/json"
}
try:
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json()
except requests.exceptions.HTTPError as e:
print(f"Failed to list connections: {e.response.status_code}")
raise
# Example usage to find the ID
# connections = list_personal_connections(ORG_ID, ACCESS_TOKEN)
# target_account_id = connections[0]['id'] # In production, filter by name or number
JavaScript: List Accounts
async function listPersonalConnections(orgId, accessToken) {
const url = `https://${orgId}.mypurecloud.com/api/v2/contactcenter/personalconnections`;
const response = await fetch(url, {
method: 'GET',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Accept': 'application/json'
}
});
if (!response.ok) {
throw new Error(`Failed to list connections: ${response.status}`);
}
return await response.json();
}
Step 2: Trigger the Outbound Call
The core action is performed via the POST /api/v2/contactcenter/personalconnections/{personalConnectionAccountId}/calls endpoint.
Required Scope: personalconnection:write
Request Payload Structure
The request body must contain the to field (the recipient’s phone number) and optionally the from field (if you want to override the default number associated with the account, though usually, the account determines the from number).
JSON Payload Example:
{
"to": "+15551234567",
"from": "+15559876543"
}
Note: If from is omitted, the system uses the primary number associated with the personalConnectionAccountId.
Python: Trigger Call
import requests
import json
def trigger_outbound_call(org_id: str, access_token: str, account_id: str, to_number: str, from_number: str = None) -> dict:
"""
Triggers an outbound call via Personal Connection.
Args:
org_id: CXone Organization ID
access_token: OAuth2 Bearer Token
account_id: The ID of the Personal Connection account
to_number: The recipient's phone number (E.164 format)
from_number: Optional. The caller ID number. Defaults to account's primary number.
Returns:
The response JSON containing the call ID and status.
"""
url = f"https://{org_id}.mypurecloud.com/api/v2/contactcenter/personalconnections/{account_id}/calls"
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json",
"Accept": "application/json"
}
payload = {
"to": to_number
}
if from_number:
payload["from"] = from_number
try:
response = requests.post(url, json=payload, headers=headers)
# Handle common errors
if response.status_code == 401:
print("Error: Unauthorized. Token may be expired or invalid.")
raise Exception("Unauthorized")
elif response.status_code == 403:
print("Error: Forbidden. Check if 'personalconnection:write' scope is granted.")
raise Exception("Forbidden")
elif response.status_code == 404:
print("Error: Not Found. The Personal Connection Account ID does not exist.")
raise Exception("Not Found")
elif response.status_code == 429:
print("Error: Too Many Requests. Rate limit exceeded.")
raise Exception("Rate Limit Exceeded")
response.raise_for_status()
return response.json()
except requests.exceptions.HTTPError as e:
print(f"HTTP Error: {e.response.status_code} - {e.response.text}")
raise
except Exception as e:
print(f"An error occurred: {e}")
raise
JavaScript: Trigger Call
async function triggerOutboundCall(orgId, accessToken, accountId, toNumber, fromNumber = null) {
const url = `https://${orgId}.mypurecloud.com/api/v2/contactcenter/personalconnections/${accountId}/calls`;
const payload = {
to: toNumber
};
if (fromNumber) {
payload.from = fromNumber;
}
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(payload)
});
if (response.status === 401) {
throw new Error("Unauthorized. Token may be expired.");
} else if (response.status === 403) {
throw new Error("Forbidden. Check OAuth scopes.");
} else if (response.status === 404) {
throw new Error("Not Found. Invalid Personal Connection Account ID.");
} else if (response.status === 429) {
throw new Error("Rate Limit Exceeded.");
}
if (!response.ok) {
const errorText = await response.text();
throw new Error(`API Error: ${response.status} - ${errorText}`);
}
return await response.json();
} catch (error) {
console.error("Call trigger error:", error);
throw error;
}
}
Step 3: Processing the Response
The API returns a 200 OK response with a JSON body containing the details of the initiated call. The most critical field is callId, which you can use for subsequent lookups or logging.
Successful Response Example:
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"personalConnectionAccountId": "pc-12345",
"to": "+15551234567",
"from": "+15559876543",
"status": "initiated",
"createdTime": "2023-10-27T10:00:00.000Z"
}
You should log the id field. This ID corresponds to the call instance in CXone. You can use this ID to fetch call details later if you need to verify if the call was answered, missed, or failed.
Python: Handling Response
# Continuing from trigger_outbound_call function
call_result = trigger_outbound_call(ORG_ID, ACCESS_TOKEN, target_account_id, "+15551234567")
if call_result:
call_id = call_result.get("id")
status = call_result.get("status")
print(f"Call initiated successfully. ID: {call_id}, Status: {status}")
# In a production environment, you might store this call_id in a database
# to correlate with future webhooks or status checks.
JavaScript: Handling Response
// Continuing from triggerOutboundCall function
const callResult = await triggerOutboundCall(ORG_ID, accessToken, targetAccountId, "+15551234567");
if (callResult) {
const callId = callResult.id;
const status = callResult.status;
console.log(`Call initiated successfully. ID: ${callId}, Status: ${status}`);
}
Complete Working Example
Below is a complete, runnable Python script. Replace the placeholder credentials with your actual CXone details.
import requests
import sys
# --- Configuration ---
ORG_ID = "your-org-id"
CLIENT_ID = "your-client-id"
CLIENT_SECRET = "your-client-secret"
PERSONAL_CONNECTION_ACCOUNT_ID = "your-pc-account-id"
RECIPIENT_PHONE_NUMBER = "+15551234567"
# Optional: Caller ID. If None, uses the PC account's default number.
CALLER_ID_NUMBER = None
# --- Functions ---
def get_token():
url = f"https://{ORG_ID}.mypurecloud.com/api/v2/oauth/token"
payload = {
"grant_type": "client_credentials",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET
}
headers = {"Content-Type": "application/json"}
try:
response = requests.post(url, json=payload, headers=headers)
response.raise_for_status()
return response.json()["access_token"]
except Exception as e:
print(f"Failed to get token: {e}")
sys.exit(1)
def make_call(access_token):
url = f"https://{ORG_ID}.mypurecloud.com/api/v2/contactcenter/personalconnections/{PERSONAL_CONNECTION_ACCOUNT_ID}/calls"
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json",
"Accept": "application/json"
}
payload = {
"to": RECIPIENT_PHONE_NUMBER
}
if CALLER_ID_NUMBER:
payload["from"] = CALLER_ID_NUMBER
try:
response = requests.post(url, json=payload, headers=headers)
if response.status_code == 200:
result = response.json()
print(f"SUCCESS: Call initiated. ID: {result['id']}, Status: {result['status']}")
return result
else:
print(f"FAILED: Status Code {response.status_code}")
print(f"Response: {response.text}")
return None
except Exception as e:
print(f"Error making call: {e}")
return None
# --- Main Execution ---
if __name__ == "__main__":
print("1. Authenticating...")
token = get_token()
print("2. Initiating Outbound Call...")
make_call(token)
print("3. Done.")
Common Errors & Debugging
Error: 401 Unauthorized
- What causes it: The OAuth token is expired, invalid, or malformed.
- How to fix it: Ensure your token fetch logic is correct. Check that the
client_idandclient_secretmatch the OAuth Client in your CXone Admin Console. Verify that the token is being passed in the header asBearer <token>.
Error: 403 Forbidden
- What causes it: The OAuth Client does not have the required scope.
- How to fix it: Go to the CXone Admin Console > Platform > OAuth Clients. Select your client and ensure the
personalconnection:writescope is checked. Note that scope changes may require re-issuing the token or waiting for propagation.
Error: 404 Not Found
- What causes it: The
personalConnectionAccountIdprovided in the URL does not exist or is not accessible by the user associated with the OAuth client. - How to fix it: Use the
GET /api/v2/contactcenter/personalconnectionsendpoint to list all available accounts and copy the correctid. Ensure the account is active.
Error: 400 Bad Request
- What causes it: The phone number format is invalid or missing required fields.
- How to fix it: Ensure the
tonumber is in E.164 format (e.g.,+15551234567). The+sign is required. Check that the JSON payload is valid.
Error: 429 Too Many Requests
- What causes it: You have exceeded the rate limit for the Personal Connection API.
- How to fix it: Implement exponential backoff in your code. The response headers usually include
Retry-After. Do not retry immediately; wait for the specified duration.