Troubleshooting CXone GET /agents/states Returning Empty Arrays
What You Will Build
- A diagnostic Python script that queries the NICE CXone Agent States API to retrieve real-time availability for a specific agent.
- Logic to validate OAuth token scopes and filter results to identify why an agent appears offline or missing from the response.
- Python code using the
requestslibrary with explicit error handling for 401, 403, and 429 responses.
Prerequisites
- OAuth Client Type: Server-to-Server (Client Credentials) or User-to-Server (OAuth2) with active session.
- Required Scopes:
agents:agent:readandagents:state:read. Without these, the API returns an empty array or a 403 Forbidden error. - SDK/API Version: NICE CXone REST API v2.
- Language/Runtime: Python 3.8+.
- External Dependencies:
requests,pyjwt(optional, for token debugging).
pip install requests pyjwt
Authentication Setup
The most common cause of an empty array in CXone is an OAuth token that lacks the specific agents:state:read scope. CXone uses fine-grained scopes. A token with agents:agent:read allows you to see agent profiles but not their real-time state.
This section demonstrates how to obtain a token and verify its scopes before making the API call.
import requests
import json
import jwt
# Configuration
CXONE_DOMAIN = "niceincontact.com" # Change to your region, e.g., nicecxone.com
CLIENT_ID = "YOUR_CLIENT_ID"
CLIENT_SECRET = "YOUR_CLIENT_SECRET"
AUTH_URL = f"https://{CLIENT_ID}.{CXONE_DOMAIN}/oauth/token"
def get_access_token():
"""
Retrieves an OAuth2 access token using Client Credentials flow.
"""
payload = {
"grant_type": "client_credentials",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"scope": "agents:agent:read agents:state:read" # Critical: Include state:read
}
headers = {
"Content-Type": "application/x-www-form-urlencoded"
}
try:
response = requests.post(AUTH_URL, data=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
def verify_token_scopes(token: str):
"""
Decodes the JWT to verify it contains the required scopes.
"""
try:
# CXone tokens are signed with RS256. We decode without verification for inspection.
# In production, verify the signature using the public key from the JWKS endpoint.
decoded = jwt.decode(token, options={"verify_signature": False})
scopes = decoded.get("scope", "")
print(f"Token Scopes: {scopes}")
required_scopes = ["agents:agent:read", "agents:state:read"]
for req_scope in required_scopes:
if req_scope not in scopes:
raise ValueError(f"Missing required scope: {req_scope}")
return True
except Exception as e:
print(f"Token verification failed: {e}")
return False
# Initial Auth
token = get_access_token()
if verify_token_scopes(token):
print("Token is valid and has required scopes.")
else:
exit(1)
Implementation
Step 1: Constructing the Agent States Request
The endpoint GET /api/v2/agents/states returns a list of all agents currently in the system who have an active state. It does not return agents who are completely logged out (state is null/empty).
Key Parameter: agentIds
If you do not provide agentIds, the API returns all agents with a state. If you are looking for a specific agent and do not see them, they are either logged out or the ID is incorrect.
Endpoint: GET /api/v2/agents/states
Method: GET
Headers: Authorization: Bearer <token>, Content-Type: application/json
def get_agent_states(token: str, agent_ids: list[str] = None):
"""
Fetches agent states from CXone.
Args:
token: Valid OAuth2 access token.
agent_ids: Optional list of agent IDs to filter by. If None, returns all active agents.
Returns:
dict: The JSON response from the API.
"""
base_url = f"https://{CLIENT_ID}.{CXONE_DOMAIN}/api/v2/agents/states"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json"
}
params = {}
if agent_ids:
# CXone API expects comma-separated IDs for this endpoint
params["agentIds"] = ",".join(agent_ids)
try:
response = requests.get(base_url, headers=headers, params=params)
# Handle 429 Rate Limiting
if response.status_code == 429:
retry_after = response.headers.get("Retry-After", 1)
print(f"Rate limited (429). Retrying after {retry_after} seconds...")
import time
time.sleep(int(retry_after))
response = requests.get(base_url, headers=headers, params=params)
response.raise_for_status()
return response.json()
except requests.exceptions.HTTPError as e:
print(f"API Error: {e.response.status_code}")
print(f"Response Body: {e.response.text}")
if e.response.status_code == 401:
print("Error: Token is invalid or expired. Re-authenticate.")
elif e.response.status_code == 403:
print("Error: Forbidden. Check if 'agents:state:read' scope is present.")
elif e.response.status_code == 404:
print("Error: Endpoint not found. Check base URL and subdomain.")
raise
except Exception as e:
print(f"Unexpected error: {e}")
raise
# Example Usage
agent_id_to_find = "12345678-1234-1234-1234-123456789012" # Replace with actual UUID
result = get_agent_states(token, agent_ids=[agent_id_to_find])
print(json.dumps(result, indent=2))
Step 2: Analyzing the Response Structure
The response is a JSON object containing an items array. Each item represents an agent with a current state.
Realistic Response Body (Agent Logged In):
{
"items": [
{
"id": "12345678-1234-1234-1234-123456789012",
"name": "John Doe",
"email": "john.doe@example.com",
"state": {
"id": "8b7c6d5e-4f3a-2b1c-0d9e-8f7a6b5c4d3e",
"name": "Available",
"description": "Ready to receive interactions",
"color": "#00FF00",
"skillLevels": [
{
"skillId": "skill-uuid-123",
"name": "English",
"level": 100
}
]
},
"lastUpdatedTimestamp": "2023-10-27T10:00:00.000Z"
}
],
"page": 1,
"pageSize": 100,
"totalElements": 1,
"totalPages": 1
}
Realistic Response Body (Agent NOT in response):
{
"items": [],
"page": 1,
"pageSize": 100,
"totalElements": 0,
"totalPages": 0
}
If you receive items: [] when querying a specific agentId, the agent is not in the system with an active state. This means they are logged out, or the ID is wrong.
Step 3: Debugging “Empty Array” Scenarios
There are three primary reasons for an empty array when you expect an agent to be present. We will build a diagnostic function to rule them out.
- Wrong Agent ID: You are querying by Email or Name, but the API requires the UUID.
- Agent is Logged Out: The agent has no active state record. The API only returns agents with a state.
- Skill/Queue Mismatch (Advanced): In some configurations, if an agent is in a state that has no skill levels defined, they may appear differently, but they should still appear in the list.
def diagnose_agent_visibility(token: str, search_identifier: str):
"""
Diagnoses why an agent is not appearing in the states list.
Args:
token: Valid OAuth2 access token.
search_identifier: The Agent ID, Email, or Name to search for.
"""
base_url = f"https://{CLIENT_ID}.{CXONE_DOMAIN}"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
print(f"\n--- Diagnosing Agent: {search_identifier} ---")
# Step 1: Verify Agent Exists and Get UUID
# We use the Agent Management API to find the UUID by email or name if ID is not provided
agent_uuid = None
# Check if input looks like a UUID
if len(search_identifier) == 36 and search_identifier.count('-') == 4:
agent_uuid = search_identifier
print("Input identified as UUID. Proceeding to state check.")
else:
print("Input not a UUID. Searching Agent Management API...")
# Search agents by email or name
search_url = f"{base_url}/api/v2/agents"
params = {
"filter": f"email eq '{search_identifier}' or name eq '{search_identifier}'",
"pageSize": 10
}
resp = requests.get(search_url, headers=headers, params=params)
if resp.status_code == 200:
data = resp.json()
if data.get("items"):
agent_uuid = data["items"][0]["id"]
print(f"Found Agent UUID: {agent_uuid}")
else:
print("ERROR: Agent not found in CXone directory. Check spelling or existence.")
return
else:
print(f"ERROR: Failed to search agents. Status: {resp.status_code}")
return
# Step 2: Check State with Correct UUID
if agent_uuid:
state_url = f"{base_url}/api/v2/agents/states"
params = {"agentIds": agent_uuid}
resp = requests.get(state_url, headers=headers, params=params)
if resp.status_code == 200:
state_data = resp.json()
if state_data.get("items"):
agent_state = state_data["items"][0]
print(f"SUCCESS: Agent is ONLINE.")
print(f"Current State: {agent_state['state']['name']}")
print(f"Last Updated: {agent_state['lastUpdatedTimestamp']}")
else:
print("RESULT: Agent exists but has NO ACTIVE STATE.")
print("Explanation: The agent is logged out or in a 'offline' state that CXone does not track in the /states endpoint.")
print("Action: Ask the agent to log in and select a state (e.g., Available, Break).")
else:
print(f"ERROR: Failed to fetch state. Status: {resp.status_code}")
print(f"Response: {resp.text}")
diagnose_agent_visibility(token, "agent.email@company.com")
Complete Working Example
This script combines authentication, scope verification, and diagnostic logic into a single runnable module.
import requests
import json
import sys
import time
# --- CONFIGURATION ---
CXONE_SUBDOMAIN = "your-subdomain" # e.g., niceincontact.com
CLIENT_ID = "your-client-id"
CLIENT_SECRET = "your-client-secret"
SEARCH_TERM = "agent.email@company.com" # Or UUID
# --- HELPER FUNCTIONS ---
def get_token():
url = f"https://{CLIENT_ID}.{CXONE_SUBDOMAIN}/oauth/token"
data = {
"grant_type": "client_credentials",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"scope": "agents:agent:read agents:state:read"
}
headers = {"Content-Type": "application/x-www-form-urlencoded"}
try:
r = requests.post(url, data=data, headers=headers)
r.raise_for_status()
return r.json()["access_token"]
except Exception as e:
print(f"Auth Failed: {e}")
sys.exit(1)
def get_agent_uuid(token, search_term):
"""Finds UUID by email/name if not a UUID."""
is_uuid = len(search_term) == 36 and search_term.count('-') == 4
if is_uuid:
return search_term
url = f"https://{CLIENT_ID}.{CXONE_SUBDOMAIN}/api/v2/agents"
headers = {"Authorization": f"Bearer {token}"}
params = {
"filter": f"email eq '{search_term}' or name eq '{search_term}'",
"pageSize": 5
}
r = requests.get(url, headers=headers, params=params)
if r.status_code == 200:
items = r.json().get("items", [])
if items:
return items[0]["id"]
else:
print("Agent not found in directory.")
return None
else:
print(f"Agent search failed: {r.status_code}")
return None
def check_agent_state(token, agent_id):
"""Checks if agent has an active state."""
url = f"https://{CLIENT_ID}.{CXONE_SUBDOMAIN}/api/v2/agents/states"
headers = {"Authorization": f"Bearer {token}"}
params = {"agentIds": agent_id}
r = requests.get(url, headers=headers, params=params)
if r.status_code == 429:
print("Rate limited. Waiting 1s...")
time.sleep(1)
r = requests.get(url, headers=headers, params=params)
if r.status_code != 200:
print(f"State API Error: {r.status_code} - {r.text}")
return False
data = r.json()
if data.get("items"):
return data["items"][0]
return None
# --- MAIN EXECUTION ---
if __name__ == "__main__":
print("Starting CXone Agent State Diagnostic...")
# 1. Authenticate
token = get_token()
print("Authenticated successfully.")
# 2. Find Agent UUID
agent_id = get_agent_uuid(token, SEARCH_TERM)
if not agent_id:
print("Cannot proceed. Agent not found.")
sys.exit(1)
print(f"Found Agent ID: {agent_id}")
# 3. Check State
state_info = check_agent_state(token, agent_id)
if state_info:
print("\n--- RESULT: Agent is ACTIVE ---")
print(f"State Name: {state_info['state']['name']}")
print(f"State ID: {state_info['state']['id']}")
print(f"Last Updated: {state_info['lastUpdatedTimestamp']}")
else:
print("\n--- RESULT: Agent is INACTIVE ---")
print("The agent is not returned in the /agents/states list.")
print("This means the agent is currently LOGGED OUT.")
print("To fix this, the agent must log into the CXone Desktop/Agent Workspace.")
Common Errors & Debugging
Error: 403 Forbidden
What causes it: The OAuth token does not have the agents:state:read scope.
How to fix it: Update your OAuth client’s allowed scopes in the CXone Admin Console under Integrations > OAuth Clients. Add agents:state:read. Re-generate the token.
Error: 401 Unauthorized
What causes it: The token is expired, invalid, or the subdomain in the URL is incorrect.
How to fix it: Ensure CXONE_SUBDOMAIN matches your organization’s URL exactly. Check that the CLIENT_SECRET is correct. Tokens expire after 3600 seconds (1 hour) by default; implement token refresh logic.
Error: Empty items Array (200 OK)
What causes it:
- Agent Logged Out: The API only returns agents with an active state. Logged-out agents do not appear.
- Wrong ID: You are passing an Email or Name instead of the UUID.
- Filter Mismatch: If you use query parameters incorrectly, the filter may exclude the agent.
Debugging Code:
# Add this debug block to your check_agent_state function
if not state_info:
print("DEBUG: Querying ALL agents to see if anyone is online...")
all_states_url = f"https://{CLIENT_ID}.{CXONE_SUBDOMAIN}/api/v2/agents/states"
r = requests.get(all_states_url, headers={"Authorization": f"Bearer {token}"})
if r.status_code == 200:
all_data = r.json()
print(f"Total online agents: {all_data.get('totalElements', 0)}")
if all_data.get('items'):
print("Sample online agent:", all_data['items'][0]['name'])
else:
print("No agents are online in the system.")
Error: 429 Too Many Requests
What causes it: CXone enforces strict rate limits on the Agent States API (typically 10 requests per second per client).
How to fix it: Implement exponential backoff. The code above includes a basic retry for 429s. For production, use a library like tenacity or backoff.