Fixing CXone GET /agents/states Returning Empty Array — Agent Not Showing as Logged In

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/states with proper filtering to isolate why an agent appears offline or returns an empty state array.
  • You will implement this in Python using the requests library, 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_id and client_secret for a CXone API Client with the agent:state:view scope.
  • 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 requests library 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:

  1. The agent is not logged into any site.
  2. The agent ID is incorrect.
  3. 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:view OAuth scope, or the user associated with the client does not have the Agent State Viewer role in CXone.
  • Fix:
    1. Go to CXone Admin Console > Security > API Clients.
    2. Edit your client and ensure agent:state:view is checked.
    3. Verify the user has the correct role via Admin Console > Security > User Roles.

Error: 404 Not Found (Agent ID)

  • Cause: The agent_id provided is incorrect or belongs to a different CXone environment.
  • Fix:
    1. Verify the Agent ID from the CXone Admin Console > Agents > [Agent Name] > URL.
    2. Ensure you are using the correct BASE_URL (e.g., api-us-22 vs api-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:
    1. Ask the agent to log into the CXone Agent Desktop.
    2. Wait for the “Available” state to be selected.
    3. 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:
    1. Check which site the agent is logged into via the Agent Desktop UI.
    2. Update the SITE_ID in the script to match the active site.
    3. Alternatively, query /api/v2/agents/states without a site filter to see all active sites for the agent.

Official References