Fixing CXone GET /agents/states Returning Empty Array — Agent Not Showing as Logged In
What You Will Build
- You will build a diagnostic script that queries the NICE CXone Agent State API to verify agent login status and skill group associations.
- You will use the NICE CXone REST API endpoint
/api/v2/agents/stateswith proper filtering to isolate why an agent appears offline or returns an empty state array. - You will implement this in Python using the
requestslibrary, including robust OAuth2 authentication and error handling for common 403 and 400 responses.
Prerequisites
Before executing the code, ensure you have the following:
- OAuth Client Credentials: A valid
client_idandclient_secretfor a CXone API Client with theagent:state:viewscope. - CXone Environment URL: The base URL for your CXone instance (e.g.,
https://api-us-22.cxone.com). - Agent ID: The unique identifier of the agent you are troubleshooting.
- Python 3.8+: With the
requestslibrary installed (pip install requests). - Permissions: The API Client user must have the Agent State Viewer role or equivalent permissions to view agent states.
Authentication Setup
NICE CXone uses OAuth2 Client Credentials Flow for API access. You must obtain a short-lived access token before querying agent states. The token expires after 1 hour, so production code should cache and refresh tokens, but for diagnostic scripts, a single fetch is sufficient.
import requests
import json
import sys
def get_cxone_access_token(client_id: str, client_secret: str, base_url: str) -> str:
"""
Authenticates with CXone and returns an access token.
Args:
client_id: OAuth Client ID
client_secret: OAuth Client Secret
base_url: CXone API base URL (e.g., https://api-us-22.cxone.com)
Returns:
Access token string
Raises:
Exception: If authentication fails
"""
token_url = f"{base_url}/oauth/token"
headers = {
"Content-Type": "application/x-www-form-urlencoded"
}
data = {
"grant_type": "client_credentials",
"client_id": client_id,
"client_secret": client_secret
}
response = requests.post(token_url, headers=headers, data=data)
if response.status_code != 200:
raise Exception(f"Authentication failed: {response.status_code} - {response.text}")
token_data = response.json()
return token_data.get("access_token")
# Configuration
CLIENT_ID = "YOUR_CLIENT_ID"
CLIENT_SECRET = "YOUR_CLIENT_SECRET"
BASE_URL = "https://api-us-22.cxone.com" # Replace with your environment
try:
ACCESS_TOKEN = get_cxone_access_token(CLIENT_ID, CLIENT_SECRET, BASE_URL)
print("Authentication successful.")
except Exception as e:
print(f"Failed to authenticate: {e}")
sys.exit(1)
Implementation
Step 1: Query Agent States with Correct Filtering
The most common reason for an empty array from GET /api/v2/agents/states is incorrect filtering. By default, this endpoint may return all agents or require specific query parameters to filter by agent ID or site. If you query without filters, you might get a massive list where the target agent is hard to find, or if you filter incorrectly, you get an empty list.
The endpoint supports the agentIds query parameter to filter by specific agents.
def get_agent_states(access_token: str, base_url: str, agent_id: str = None) -> dict:
"""
Retrieves agent states from CXone.
Args:
access_token: Valid OAuth access token
base_url: CXone API base URL
agent_id: Optional specific agent ID to filter by
Returns:
JSON response from the API
"""
endpoint = "/api/v2/agents/states"
url = f"{base_url}{endpoint}"
headers = {
"Authorization": f"Bearer {access_token}",
"Accept": "application/json"
}
params = {}
if agent_id:
params["agentIds"] = agent_id
response = requests.get(url, headers=headers, params=params)
# Handle common errors
if response.status_code == 401:
raise Exception("Unauthorized: Access token is invalid or expired.")
elif response.status_code == 403:
raise Exception("Forbidden: API Client lacks 'agent:state:view' scope or permissions.")
elif response.status_code == 400:
raise Exception(f"Bad Request: {response.text}")
elif response.status_code != 200:
raise Exception(f"API Error: {response.status_code} - {response.text}")
return response.json()
# Test the query
TARGET_AGENT_ID = "YOUR_AGENT_ID" # Replace with actual agent ID
try:
states_data = get_agent_states(ACCESS_TOKEN, BASE_URL, TARGET_AGENT_ID)
print(json.dumps(states_data, indent=2))
except Exception as e:
print(f"Error retrieving states: {e}")
Step 2: Diagnose the Empty Array
If the response is [] (empty array), the issue is not necessarily that the API is broken. It usually means one of three things:
- The agent is not logged into any site.
- The agent ID is incorrect.
- The agent is logged in but has no active skills or site associations visible to the API client.
To diagnose, you must check the agent’s basic profile and login history. First, verify the agent exists and is active.
def get_agent_profile(access_token: str, base_url: str, agent_id: str) -> dict:
"""
Retrieves the basic profile of an agent to verify existence and status.
Args:
access_token: Valid OAuth access token
base_url: CXone API base URL
agent_id: Agent ID to look up
Returns:
Agent profile JSON
"""
endpoint = f"/api/v2/users/{agent_id}"
url = f"{base_url}{endpoint}"
headers = {
"Authorization": f"Bearer {access_token}",
"Accept": "application/json"
}
response = requests.get(url, headers=headers)
if response.status_code == 404:
raise Exception(f"Agent ID {agent_id} not found. Check the ID.")
elif response.status_code != 200:
raise Exception(f"Failed to fetch agent profile: {response.status_code}")
return response.json()
try:
agent_profile = get_agent_profile(ACCESS_TOKEN, BASE_URL, TARGET_AGENT_ID)
print("Agent Profile Found:")
print(f"Name: {agent_profile.get('name')}")
print(f"Status: {agent_profile.get('status')}") # Should be 'active'
print(f"Type: {agent_profile.get('type')}") # Should be 'agent' or 'supervisor'
except Exception as e:
print(f"Profile check failed: {e}")
If the agent profile is active, the next step is to check if the agent is logged into a specific site. Agent states are tied to sites. If an agent is not logged into a site, they will not appear in the states list for that site, or may appear with a generic “offline” state depending on the query.
Step 3: Check Site-Specific Agent States
CXone agent states are often scoped to a site. If you query /api/v2/agents/states without a site context, and the agent is logged into a specific site, you might get inconsistent results. More importantly, you must query the site-specific endpoint to see detailed state information.
Use GET /api/v2/sites/{siteId}/agents/states to get states for agents logged into a specific site.
def get_site_agent_states(access_token: str, base_url: str, site_id: str, agent_id: str = None) -> dict:
"""
Retrieves agent states for a specific site.
Args:
access_token: Valid OAuth access token
base_url: CXone API base URL
site_id: ID of the CXone site
agent_id: Optional agent ID to filter
Returns:
JSON response containing agent states for the site
"""
endpoint = f"/api/v2/sites/{site_id}/agents/states"
url = f"{base_url}{endpoint}"
headers = {
"Authorization": f"Bearer {access_token}",
"Accept": "application/json"
}
params = {}
if agent_id:
params["agentIds"] = agent_id
response = requests.get(url, headers=headers, params=params)
if response.status_code == 404:
raise Exception(f"Site ID {site_id} not found.")
elif response.status_code != 200:
raise Exception(f"API Error: {response.status_code} - {response.text}")
return response.json()
# You need the Site ID. This can be obtained from the CXone Admin Console or via GET /api/v2/sites
SITE_ID = "YOUR_SITE_ID" # Replace with actual site ID
try:
site_states = get_site_agent_states(ACCESS_TOKEN, BASE_URL, SITE_ID, TARGET_AGENT_ID)
print(json.dumps(site_states, indent=2))
if not site_states:
print("Agent is not logged into this specific site.")
else:
print("Agent states found for this site.")
except Exception as e:
print(f"Site state check failed: {e}")
Complete Working Example
This script combines authentication, agent profile verification, and state checking to provide a full diagnostic report.
import requests
import json
import sys
# Configuration
CLIENT_ID = "YOUR_CLIENT_ID"
CLIENT_SECRET = "YOUR_CLIENT_SECRET"
BASE_URL = "https://api-us-22.cxone.com" # Replace with your environment
TARGET_AGENT_ID = "YOUR_AGENT_ID" # Replace with actual agent ID
SITE_ID = "YOUR_SITE_ID" # Replace with actual site ID
def get_cxone_access_token(client_id: str, client_secret: str, base_url: str) -> str:
token_url = f"{base_url}/oauth/token"
headers = {"Content-Type": "application/x-www-form-urlencoded"}
data = {
"grant_type": "client_credentials",
"client_id": client_id,
"client_secret": client_secret
}
response = requests.post(token_url, headers=headers, data=data)
if response.status_code != 200:
raise Exception(f"Authentication failed: {response.status_code} - {response.text}")
return response.json().get("access_token")
def get_agent_profile(access_token: str, base_url: str, agent_id: str) -> dict:
endpoint = f"/api/v2/users/{agent_id}"
url = f"{base_url}{endpoint}"
headers = {
"Authorization": f"Bearer {access_token}",
"Accept": "application/json"
}
response = requests.get(url, headers=headers)
if response.status_code == 404:
raise Exception(f"Agent ID {agent_id} not found.")
if response.status_code != 200:
raise Exception(f"Failed to fetch agent profile: {response.status_code}")
return response.json()
def get_global_agent_states(access_token: str, base_url: str, agent_id: str) -> list:
endpoint = "/api/v2/agents/states"
url = f"{base_url}{endpoint}"
headers = {
"Authorization": f"Bearer {access_token}",
"Accept": "application/json"
}
params = {"agentIds": agent_id}
response = requests.get(url, headers=headers, params=params)
if response.status_code != 200:
raise Exception(f"Global states API Error: {response.status_code} - {response.text}")
return response.json()
def get_site_agent_states(access_token: str, base_url: str, site_id: str, agent_id: str) -> list:
endpoint = f"/api/v2/sites/{site_id}/agents/states"
url = f"{base_url}{endpoint}"
headers = {
"Authorization": f"Bearer {access_token}",
"Accept": "application/json"
}
params = {"agentIds": agent_id}
response = requests.get(url, headers=headers, params=params)
if response.status_code == 404:
raise Exception(f"Site ID {site_id} not found.")
if response.status_code != 200:
raise Exception(f"Site states API Error: {response.status_code} - {response.text}")
return response.json()
def main():
try:
print("1. Authenticating...")
access_token = get_cxone_access_token(CLIENT_ID, CLIENT_SECRET, BASE_URL)
print("2. Checking Agent Profile...")
profile = get_agent_profile(access_token, BASE_URL, TARGET_AGENT_ID)
print(f" Agent Name: {profile.get('name')}")
print(f" Agent Status: {profile.get('status')}")
if profile.get('status') != 'active':
print(" WARNING: Agent is not active. They cannot log in.")
return
print("3. Checking Global Agent States...")
global_states = get_global_agent_states(access_token, BASE_URL, TARGET_AGENT_ID)
if not global_states:
print(" Result: Empty array. Agent is not logged into any site or has no active states.")
else:
print(" Result: Agent has global states.")
for state in global_states:
print(f" Site: {state.get('site', {}).get('id')}, State: {state.get('state', {}).get('name')}")
print("4. Checking Site-Specific Agent States...")
try:
site_states = get_site_agent_states(access_token, BASE_URL, SITE_ID, TARGET_AGENT_ID)
if not site_states:
print(f" Result: Agent is not logged into Site {SITE_ID}.")
else:
print(f" Result: Agent is logged into Site {SITE_ID}.")
for state in site_states:
print(f" State: {state.get('state', {}).get('name')}")
print(f" Skills: {state.get('skills', [])}")
except Exception as e:
print(f" Error checking site states: {e}")
except Exception as e:
print(f"Diagnostic failed: {e}")
sys.exit(1)
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 403 Forbidden
- Cause: The API Client does not have the
agent:state:viewOAuth scope, or the user associated with the client does not have the Agent State Viewer role in CXone. - Fix:
- Go to CXone Admin Console > Security > API Clients.
- Edit your client and ensure
agent:state:viewis checked. - Verify the user has the correct role via Admin Console > Security > User Roles.
Error: 404 Not Found (Agent ID)
- Cause: The
agent_idprovided is incorrect or belongs to a different CXone environment. - Fix:
- Verify the Agent ID from the CXone Admin Console > Agents > [Agent Name] > URL.
- Ensure you are using the correct
BASE_URL(e.g.,api-us-22vsapi-eu-01).
Error: Empty Array [] from /api/v2/agents/states
- Cause: The agent is not currently logged into the CXone Agent Desktop. Agent states are only returned for agents who are actively logged in and have a session.
- Fix:
- Ask the agent to log into the CXone Agent Desktop.
- Wait for the “Available” state to be selected.
- Re-run the script. If the agent is logged in but still returns empty, check if they are assigned to the site you are querying.
Error: Empty Array [] from /api/v2/sites/{siteId}/agents/states
- Cause: The agent is logged in, but not to the specified
site_id. Agents can be associated with multiple sites, but they must explicitly log into a site to appear in that site’s state list. - Fix:
- Check which site the agent is logged into via the Agent Desktop UI.
- Update the
SITE_IDin the script to match the active site. - Alternatively, query
/api/v2/agents/stateswithout a site filter to see all active sites for the agent.