Fixing Empty Agent States in CXone: Debugging Login Visibility via API
What You Will Build
- You will build a Python script that authenticates with NICE CXone, queries the agent state endpoint, and correctly interprets the response to identify why an agent appears offline or missing.
- This tutorial uses the NICE CXone REST API specifically targeting the
/api/v2/agents/statesand/api/v2/agents/{agentId}/stateendpoints. - The implementation is written in Python 3.9+ using the
requestslibrary for HTTP interactions anddatetimefor timezone handling.
Prerequisites
Before running the code, ensure you have the following credentials and configuration:
- OAuth Client Credentials: You need a Client ID and Client Secret for a CXone application.
- Required OAuth Scopes: The client must have the
agent:readscope. If you intend to update states, you needagent:write. For this debugging tutorial,agent:readis sufficient. - Organization ID: Your unique CXone Organization ID (e.g.,
12345678-1234-1234-1234-123456789012). - Agent ID: The unique identifier for the agent you are investigating. You can find this in the CXone Admin Console under Agents or by querying the
/api/v2/agentsendpoint. - Python Environment: Install the required package:
pip install requests python-dateutil
Authentication Setup
CXone uses OAuth 2.0 for API authentication. You must obtain an access token before making any calls. The token expires after a specific duration (typically 1 hour), so production code should implement a refresh mechanism. For this tutorial, we will fetch a fresh token for every run to ensure clarity.
Step 1: Obtaining the Access Token
The token endpoint is https://auth.nicecxone.com/as/token.oauth2. You must send your Client ID and Client Secret as Basic Authentication headers.
import requests
import base64
import json
from typing import Optional
def get_access_token(client_id: str, client_secret: str, org_id: str) -> Optional[str]:
"""
Authenticates with CXone OAuth and returns an access token.
Args:
client_id: The OAuth Client ID.
client_secret: The OAuth Client Secret.
org_id: The CXone Organization ID.
Returns:
The access token string or None if authentication fails.
"""
auth_url = "https://auth.nicecxone.com/as/token.oauth2"
# Encode Client ID and Secret for Basic Auth header
credentials = f"{client_id}:{client_secret}"
encoded_credentials = base64.b64encode(credentials.encode('utf-8')).decode('utf-8')
headers = {
"Authorization": f"Basic {encoded_credentials}",
"Content-Type": "application/x-www-form-urlencoded"
}
# The body must include the org_id for CXone
data = {
"grant_type": "client_credentials",
"org_id": org_id
}
try:
response = requests.post(auth_url, headers=headers, data=data)
response.raise_for_status()
token_data = response.json()
return token_data.get("access_token")
except requests.exceptions.HTTPError as e:
if response.status_code == 401:
print("Authentication failed: Check Client ID, Secret, or Org ID.")
elif response.status_code == 403:
print("Forbidden: The client may not have the required scopes.")
else:
print(f"HTTP Error: {e}")
return None
except Exception as e:
print(f"An error occurred during authentication: {e}")
return None
Implementation
Step 2: Querying Agent States Correctly
The most common reason for an empty array in /api/v2/agents/states is a misunderstanding of the endpoint’s filtering behavior. By default, this endpoint returns the current state of all agents in the organization. However, if you are looking for a specific agent, querying the global list and filtering locally is inefficient and prone to pagination issues if you have thousands of agents.
A more robust approach for debugging a specific agent is to query the specific agent’s state endpoint: /api/v2/agents/{agentId}/state.
Why /api/v2/agents/states might return an empty array for a specific agent:
- The agent is not logged in: CXone often excludes agents who are completely offline from certain state queries depending on the query parameters used.
- Pagination: The default limit is 100. If you are searching for an agent in a large organization and do not handle pagination, you might miss them.
- State Filtering: Some implementations of the API allow filtering by
stateCode. If you filter forAvailableand the agent isUnavailable, they will not appear.
Code: Querying a Specific Agent’s State
This is the recommended method for debugging a single agent. It bypasses pagination and filtering ambiguities.
def get_specific_agent_state(access_token: str, org_id: str, agent_id: str) -> dict:
"""
Retrieves the current state for a specific agent by ID.
Args:
access_token: The OAuth access token.
org_id: The CXone Organization ID.
agent_id: The unique ID of the agent.
Returns:
A dictionary containing the agent's state information.
"""
api_url = f"https://{org_id}.api.nicecxone.com/api/v2/agents/{agent_id}/state"
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json",
"Accept": "application/json"
}
try:
response = requests.get(api_url, headers=headers)
# Handle 404: Agent does not exist or ID is incorrect
if response.status_code == 404:
print(f"Agent with ID {agent_id} not found. Check the Agent ID.")
return {}
# Handle 403: Insufficient permissions
if response.status_code == 403:
print("Forbidden: Ensure the client has 'agent:read' scope.")
return {}
response.raise_for_status()
return response.json()
except requests.exceptions.HTTPError as e:
print(f"HTTP Error: {e}")
return {}
except Exception as e:
print(f"An error occurred: {e}")
return {}
Step 3: Interpreting the Response and Identifying “Logged In” Status
The response from /api/v2/agents/{agent_id}/state contains a state object. The critical field is stateCode.
LOGGED_IN: The agent has authenticated into the CXone application.LOGGED_OUT: The agent has not authenticated or has explicitly logged out.AVAILABLE: The agent is logged in and ready to take interactions.UNAVAILABLE: The agent is logged in but is on a break, meeting, or otherwise not ready.
If you are using the global /api/v2/agents/states endpoint, the response is a list. Here is how to parse it correctly to find agents who are effectively “offline” or missing.
def analyze_global_agent_states(access_token: str, org_id: str, target_agent_id: str) -> None:
"""
Queries all agent states and checks for the target agent.
Useful for verifying if the agent appears in the global list at all.
Args:
access_token: The OAuth access token.
org_id: The CXone Organization ID.
target_agent_id: The ID of the agent being debugged.
"""
api_url = f"https://{org_id}.api.nicecxone.com/api/v2/agents/states"
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json",
"Accept": "application/json"
}
# Parameters to ensure we get all agents, not just active ones
# Note: CXone API v2 agents/states defaults to returning all logged-in agents.
# To see ALL agents including offline, you may need to use /api/v2/agents and cross-reference.
# However, let's check the standard behavior first.
try:
response = requests.get(api_url, headers=headers)
response.raise_for_status()
data = response.json()
agents_list = data.get("entities", [])
print(f"Total agents returned in /agents/states: {len(agents_list)}")
target_agent = None
for agent in agents_list:
if agent.get("agentId") == target_agent_id:
target_agent = agent
break
if target_agent:
print("\n--- Target Agent Found in Global List ---")
print(json.dumps(target_agent, indent=2))
else:
print("\n--- Target Agent NOT FOUND in Global List ---")
print("Reason: The agent is likely LOGGED OUT.")
print("CXone /agents/states typically only returns agents who are currently LOGGED IN.")
print("To verify, check the specific agent endpoint below.")
except requests.exceptions.HTTPError as e:
print(f"HTTP Error: {e}")
except Exception as e:
print(f"An error occurred: {e}")
Step 4: Cross-Referencing with Agent Profile
If the agent is not appearing in /agents/states and the specific state endpoint returns LOGGED_OUT, you should verify the agent’s profile status. An agent might be disabled or deactivated.
def get_agent_profile(access_token: str, org_id: str, agent_id: str) -> dict:
"""
Retrieves the basic profile of an agent to check if they are active.
Args:
access_token: The OAuth access token.
org_id: The CXone Organization ID.
agent_id: The unique ID of the agent.
Returns:
A dictionary containing the agent's profile information.
"""
api_url = f"https://{org_id}.api.nicecxone.com/api/v2/agents/{agent_id}"
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json",
"Accept": "application/json"
}
try:
response = requests.get(api_url, headers=headers)
response.raise_for_status()
return response.json()
except requests.exceptions.HTTPError as e:
if response.status_code == 404:
print(f"Agent {agent_id} does not exist in the organization.")
else:
print(f"HTTP Error: {e}")
return {}
except Exception as e:
print(f"An error occurred: {e}")
return {}
Complete Working Example
This script combines the authentication, specific state check, and global list check into a single debugging utility.
import requests
import base64
import json
import sys
def get_access_token(client_id: str, client_secret: str, org_id: str) -> str:
auth_url = "https://auth.nicecxone.com/as/token.oauth2"
credentials = f"{client_id}:{client_secret}"
encoded_credentials = base64.b64encode(credentials.encode('utf-8')).decode('utf-8')
headers = {
"Authorization": f"Basic {encoded_credentials}",
"Content-Type": "application/x-www-form-urlencoded"
}
data = {
"grant_type": "client_credentials",
"org_id": org_id
}
try:
response = requests.post(auth_url, headers=headers, data=data)
response.raise_for_status()
return response.json().get("access_token")
except Exception as e:
print(f"Auth failed: {e}")
sys.exit(1)
def debug_agent_status(org_id: str, client_id: str, client_secret: str, agent_id: str):
print(f"--- Debugging Agent {agent_id} in Org {org_id} ---")
# 1. Authenticate
token = get_access_token(client_id, client_secret, org_id)
if not token:
return
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json"
}
# 2. Check Specific Agent State
print("\n1. Checking Specific Agent State Endpoint...")
state_url = f"https://{org_id}.api.nicecxone.com/api/v2/agents/{agent_id}/state"
try:
res_state = requests.get(state_url, headers=headers)
if res_state.status_code == 404:
print(" Result: Agent ID not found. Verify the ID.")
return
if res_state.status_code != 200:
print(f" Error: {res_state.status_code} - {res_state.text}")
return
state_data = res_state.json()
current_state_code = state_data.get("state", {}).get("stateCode", "UNKNOWN")
print(f" Current State Code: {current_state_code}")
if current_state_code == "LOGGED_OUT":
print(" Diagnosis: Agent is logged out. They will NOT appear in /agents/states.")
print(" Action: Ask the agent to log in via the CXone agent desktop.")
elif current_state_code == "LOGGED_IN":
print(" Diagnosis: Agent is logged in. Checking global list visibility...")
else:
print(f" Diagnosis: Agent is in state {current_state_code}.")
except Exception as e:
print(f" Error fetching state: {e}")
# 3. Check Global Agent States List
print("\n2. Checking Global /agents/states Endpoint...")
global_url = f"https://{org_id}.api.nicecxone.com/api/v2/agents/states"
try:
res_global = requests.get(global_url, headers=headers)
if res_global.status_code != 200:
print(f" Error: {res_global.status_code}")
return
global_data = res_global.json()
entities = global_data.get("entities", [])
found = False
for agent in entities:
if agent.get("agentId") == agent_id:
found = True
print(f" Result: Agent FOUND in global list.")
print(f" Global State: {agent.get('state', {}).get('stateCode')}")
break
if not found:
print(f" Result: Agent NOT FOUND in global list.")
print(" Diagnosis: This confirms the agent is not logged in.")
print(" Note: /agents/states only returns agents who are currently LOGGED IN.")
except Exception as e:
print(f" Error fetching global states: {e}")
# 4. Verify Agent Profile (Optional but helpful)
print("\n3. Verifying Agent Profile...")
profile_url = f"https://{org_id}.api.nicecxone.com/api/v2/agents/{agent_id}"
try:
res_profile = requests.get(profile_url, headers=headers)
if res_profile.status_code == 200:
profile = res_profile.json()
is_active = profile.get("active", False)
print(f" Profile Active: {is_active}")
if not is_active:
print(" Diagnosis: Agent profile is inactive. They cannot log in.")
print(" Action: Activate the agent in the Admin Console.")
elif res_profile.status_code == 404:
print(" Diagnosis: Agent profile does not exist.")
else:
print(f" Error: {res_profile.status_code}")
except Exception as e:
print(f" Error fetching profile: {e}")
if __name__ == "__main__":
# Replace with your actual credentials
CLIENT_ID = "YOUR_CLIENT_ID"
CLIENT_SECRET = "YOUR_CLIENT_SECRET"
ORG_ID = "YOUR_ORG_ID"
AGENT_ID = "YOUR_AGENT_ID"
if CLIENT_ID == "YOUR_CLIENT_ID":
print("Please update the script with your actual credentials.")
else:
debug_agent_status(ORG_ID, CLIENT_ID, CLIENT_SECRET, AGENT_ID)
Common Errors & Debugging
Error: 401 Unauthorized
- Cause: Invalid Client ID, Client Secret, or Expired Token.
- Fix: Verify your credentials in the CXone Admin Console under Integration > OAuth. Ensure the token was generated recently.
- Code Check: Ensure the
Authorizationheader is formatted asBearer <token>with a space.
Error: 403 Forbidden
- Cause: The OAuth client does not have the
agent:readscope. - Fix: Go to Integration > OAuth > Select Client > Scopes. Add
agent:read. Save and regenerate the token. - Note: Some organizations restrict API access to certain groups. Ensure the client is associated with a group that has permission to view agent data.
Error: 404 Not Found on /agents/{id}/state
- Cause: The
agent_idprovided is incorrect or does not exist in the organization. - Fix: Query
/api/v2/agentsto list all agents and find the correctid. Do not use the agent’s email or name as the ID. - Code Check: Ensure
agent_idis a string and matches the UUID format.
Error: Empty Array in /agents/states
- Cause: No agents are currently logged in.
- Fix: This is expected behavior. The
/agents/statesendpoint is a live view of logged-in agents. If everyone is offline, the list is empty. - Alternative: To see all agents regardless of login status, use
/api/v2/agentsand check theactiveandlogged_infields (if available in that specific response version) or cross-reference with the state endpoint.
Error: Agent Shows LOGGED_OUT but User Says They Are Logged In
- Cause: Session Timeout or Browser Cache.
- Fix: Ask the agent to refresh their browser or log out and log back in. CXone sessions can expire if idle. The API reflects the server-side state, which may lag slightly behind the client UI or become desynchronized.