Querying Agent State History in NICE CXone Using Python and the Reporting API v2
What You Will Build
- You will build a Python script that retrieves the complete interaction and state history for a specific agent over the last 24 hours using the NICE CXone Reporting API.
- This tutorial uses the NICE CXone REST API v2 endpoint
GET /api/v2/reporting/agents/{agentId}/history. - The implementation is written in Python 3.9+ using the
requestslibrary for HTTP communication and JSON parsing.
Prerequisites
- OAuth Client: A NICE CXone OAuth client with the
read:agentandread:reportingscopes. - Agent ID: The unique UUID of the agent whose history you want to retrieve.
- Python Environment: Python 3.9 or higher.
- Dependencies:
requests: For making HTTP requests.python-dotenv: For managing environment variables securely.
Install the dependencies using pip:
pip install requests python-dotenv
Authentication Setup
NICE CXone uses OAuth 2.0 for authentication. You must obtain an access token before making any API calls. This tutorial uses the Client Credentials Grant flow, which is suitable for server-to-server integrations.
Create a .env file in your project root with the following variables:
CXONE_CLIENT_ID=your_client_id
CXONE_CLIENT_SECRET=your_client_secret
CXONE_API_BASE_URL=https://api-us-01.nice-incontact.com
CXONE_AGENT_ID=your_agent_uuid
The following Python function handles the token acquisition and caching. In a production environment, you should implement token refresh logic or use a library that handles this automatically. For this tutorial, we assume the token is valid for the duration of the script execution.
import os
import requests
from dotenv import load_dotenv
load_dotenv()
CXONE_CLIENT_ID = os.getenv("CXONE_CLIENT_ID")
CXONE_CLIENT_SECRET = os.getenv("CXONE_CLIENT_SECRET")
CXONE_API_BASE_URL = os.getenv("CXONE_API_BASE_URL")
CXONE_AGENT_ID = os.getenv("CXONE_AGENT_ID")
def get_access_token() -> str:
"""
Retrieves an OAuth 2.0 access token from NICE CXone.
Returns:
str: The access token.
"""
token_url = f"{CXONE_API_BASE_URL}/oauth/token"
payload = {
"grant_type": "client_credentials",
"client_id": CXONE_CLIENT_ID,
"client_secret": CXONE_CLIENT_SECRET
}
headers = {
"Content-Type": "application/x-www-form-urlencoded"
}
response = requests.post(token_url, data=payload, headers=headers)
if response.status_code != 200:
raise Exception(f"Failed to obtain access token: {response.status_code} - {response.text}")
token_data = response.json()
return token_data["access_token"]
Implementation
Step 1: Define the Time Range and Query Parameters
The NICE CXone Reporting API requires a time range to filter data. You must specify a start and end timestamp in ISO 8601 format. For a 24-hour history, you calculate the current time and subtract 24 hours.
Additionally, the GET /api/v2/reporting/agents/{agentId}/history endpoint supports pagination. You should define the pageSize and page parameters to handle large datasets.
from datetime import datetime, timedelta, timezone
def get_time_range_iso() -> tuple[str, str]:
"""
Calculates the start and end times for the last 24 hours in ISO 8601 format.
Returns:
tuple: (start_time_iso, end_time_iso)
"""
now = datetime.now(timezone.utc)
start_time = now - timedelta(hours=24)
# Format as ISO 8601 with timezone offset
start_iso = start_time.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
end_iso = now.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
return start_iso, end_iso
Step 2: Construct the API Request with Pagination Logic
The agent history endpoint returns a list of state changes. Each record includes the agent ID, the previous state, the new state, and the timestamp of the change. The API supports pagination, so you must loop through pages until no more records are returned.
The endpoint requires the read:reporting scope.
def fetch_agent_history(access_token: str, agent_id: str, start_time: str, end_time: str) -> list[dict]:
"""
Fetches agent history from NICE CXone with pagination.
Args:
access_token (str): OAuth 2.0 access token.
agent_id (str): The UUID of the agent.
start_time (str): Start time in ISO 8601 format.
end_time (str): End time in ISO 8601 format.
Returns:
list[dict]: A list of agent history records.
"""
base_url = f"{CXONE_API_BASE_URL}/api/v2/reporting/agents/{agent_id}/history"
all_records = []
page = 1
page_size = 100 # Maximum allowed page size for this endpoint
headers = {
"Authorization": f"Bearer {access_token}",
"Accept": "application/json"
}
params = {
"start": start_time,
"end": end_time,
"pageSize": page_size,
"page": page
}
while True:
try:
response = requests.get(base_url, headers=headers, params=params)
if response.status_code == 200:
data = response.json()
records = data.get("records", [])
if not records:
break
all_records.extend(records)
# Check if there are more pages
total_records = data.get("totalRecords", 0)
if len(all_records) >= total_records:
break
page += 1
params["page"] = page
elif response.status_code == 429:
# Handle rate limiting
retry_after = int(response.headers.get("Retry-After", 5))
print(f"Rate limited. Waiting {retry_after} seconds...")
import time
time.sleep(retry_after)
elif response.status_code == 401 or response.status_code == 403:
raise Exception(f"Authentication/Authorization failed: {response.status_code} - {response.text}")
else:
raise Exception(f"API request failed: {response.status_code} - {response.text}")
except requests.exceptions.RequestException as e:
raise Exception(f"Network error: {str(e)}")
return all_records
Step 3: Process and Format the Results
The raw response from the API contains a list of records. Each record includes metadata such as the agent ID, the state change details, and the timestamp. You may want to process this data to make it more readable or to aggregate statistics.
def process_agent_history(records: list[dict]) -> list[dict]:
"""
Processes the raw agent history records into a more readable format.
Args:
records (list[dict]): Raw records from the API.
Returns:
list[dict]: Processed records with formatted timestamps and state names.
"""
processed_records = []
for record in records:
timestamp = record.get("timestamp", "")
state = record.get("state", {})
# Extract state details
state_name = state.get("name", "Unknown")
state_id = state.get("id", "")
# Format timestamp for readability
try:
dt_object = datetime.fromisoformat(timestamp.replace("Z", "+00:00"))
formatted_time = dt_object.strftime("%Y-%m-%d %H:%M:%S UTC")
except ValueError:
formatted_time = timestamp
processed_record = {
"timestamp": formatted_time,
"state_name": state_name,
"state_id": state_id,
"agent_id": record.get("agentId", ""),
"previous_state": record.get("previousState", {}).get("name", "N/A")
}
processed_records.append(processed_record)
return processed_records
Complete Working Example
The following script combines all the previous steps into a single, runnable module. It authenticates, fetches the agent history, processes the records, and prints the results.
import os
import requests
from datetime import datetime, timedelta, timezone
from dotenv import load_dotenv
load_dotenv()
CXONE_CLIENT_ID = os.getenv("CXONE_CLIENT_ID")
CXONE_CLIENT_SECRET = os.getenv("CXONE_CLIENT_SECRET")
CXONE_API_BASE_URL = os.getenv("CXONE_API_BASE_URL")
CXONE_AGENT_ID = os.getenv("CXONE_AGENT_ID")
def get_access_token() -> str:
"""
Retrieves an OAuth 2.0 access token from NICE CXone.
"""
token_url = f"{CXONE_API_BASE_URL}/oauth/token"
payload = {
"grant_type": "client_credentials",
"client_id": CXONE_CLIENT_ID,
"client_secret": CXONE_CLIENT_SECRET
}
headers = {"Content-Type": "application/x-www-form-urlencoded"}
response = requests.post(token_url, data=payload, headers=headers)
if response.status_code != 200:
raise Exception(f"Failed to obtain access token: {response.status_code} - {response.text}")
return response.json()["access_token"]
def get_time_range_iso() -> tuple[str, str]:
"""
Calculates the start and end times for the last 24 hours in ISO 8601 format.
"""
now = datetime.now(timezone.utc)
start_time = now - timedelta(hours=24)
start_iso = start_time.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
end_iso = now.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
return start_iso, end_iso
def fetch_agent_history(access_token: str, agent_id: str, start_time: str, end_time: str) -> list[dict]:
"""
Fetches agent history from NICE CXone with pagination.
"""
base_url = f"{CXONE_API_BASE_URL}/api/v2/reporting/agents/{agent_id}/history"
all_records = []
page = 1
page_size = 100
headers = {
"Authorization": f"Bearer {access_token}",
"Accept": "application/json"
}
params = {
"start": start_time,
"end": end_time,
"pageSize": page_size,
"page": page
}
while True:
try:
response = requests.get(base_url, headers=headers, params=params)
if response.status_code == 200:
data = response.json()
records = data.get("records", [])
if not records:
break
all_records.extend(records)
total_records = data.get("totalRecords", 0)
if len(all_records) >= total_records:
break
page += 1
params["page"] = page
elif response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 5))
print(f"Rate limited. Waiting {retry_after} seconds...")
import time
time.sleep(retry_after)
elif response.status_code in [401, 403]:
raise Exception(f"Authentication/Authorization failed: {response.status_code}")
else:
raise Exception(f"API request failed: {response.status_code} - {response.text}")
except requests.exceptions.RequestException as e:
raise Exception(f"Network error: {str(e)}")
return all_records
def process_agent_history(records: list[dict]) -> list[dict]:
"""
Processes the raw agent history records.
"""
processed_records = []
for record in records:
timestamp = record.get("timestamp", "")
state = record.get("state", {})
state_name = state.get("name", "Unknown")
state_id = state.get("id", "")
previous_state = record.get("previousState", {}).get("name", "N/A")
try:
dt_object = datetime.fromisoformat(timestamp.replace("Z", "+00:00"))
formatted_time = dt_object.strftime("%Y-%m-%d %H:%M:%S UTC")
except ValueError:
formatted_time = timestamp
processed_records.append({
"timestamp": formatted_time,
"state_name": state_name,
"state_id": state_id,
"agent_id": record.get("agentId", ""),
"previous_state": previous_state
})
return processed_records
def main():
"""
Main execution function.
"""
try:
# Step 1: Authenticate
print("Authenticating with NICE CXone...")
access_token = get_access_token()
print("Authentication successful.")
# Step 2: Define time range
start_time, end_time = get_time_range_iso()
print(f"Fetching history from {start_time} to {end_time}")
# Step 3: Fetch data
print("Fetching agent history...")
raw_history = fetch_agent_history(access_token, CXONE_AGENT_ID, start_time, end_time)
print(f"Retrieved {len(raw_history)} records.")
# Step 4: Process data
processed_history = process_agent_history(raw_history)
# Step 5: Display results
print("\n--- Agent State History ---")
for record in processed_history:
print(f"[{record['timestamp']}] State: {record['state_name']} (from: {record['previous_state']})")
except Exception as e:
print(f"Error: {str(e)}")
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 401 Unauthorized
- Cause: The access token is invalid, expired, or missing. This can also occur if the OAuth client credentials are incorrect.
- Fix: Verify that
CXONE_CLIENT_IDandCXONE_CLIENT_SECRETare correct in your.envfile. Ensure the token request returns a 200 status code. Check that the token is being included in theAuthorizationheader asBearer <token>.
Error: 403 Forbidden
- Cause: The OAuth client does not have the required scopes (
read:reportingorread:agent) or the user associated with the client does not have permission to view the agent’s data. - Fix: Check the OAuth client configuration in the NICE CXone admin console. Ensure the
read:reportingscope is enabled. Verify that the agent ID exists and that the client has access to that agent’s data.
Error: 429 Too Many Requests
- Cause: You have exceeded the API rate limit. NICE CXone enforces rate limits to ensure fair usage.
- Fix: Implement exponential backoff and retry logic. The response header
Retry-Afterindicates how many seconds to wait before retrying. The code example above includes basic handling for this error.
Error: Empty Records
- Cause: The agent may not have had any state changes in the specified time range, or the time range is incorrect.
- Fix: Verify the
startandendtimestamps. Ensure the agent was active during the queried period. Check thetotalRecordsfield in the API response to confirm if there are indeed no records.