CXone Reporting API v2: 400 Error on Agent State History Query

Hitting the CXone Reporting API v2 endpoint for agent state history (/api/v2/reporting/agents/states/history) and getting 400 Bad Request. The OData filter seems correct but the payload keeps rejecting the time range. Here’s the JSON I’m sending:

{
 "interval": "PT1H",
 "timeFrame": "P1D",
 "groupBy": ["agentId"]
}

Any idea what’s breaking the query?

The issue isn’t the JSON body. The Reporting API v2 for agent state history relies heavily on query parameters, not the request payload. You’re sending the time frame in the body, but the endpoint expects timeRange as a query string parameter with ISO 8601 duration syntax.

Here’s how I structure these calls in my WEM adherence scripts. I usually use curl to test before integrating into Python or Node.

curl -X GET \
 'https://api.nice.incontact.com/CXoneapi/v2/reporting/agents/states/history?timeRange=PT24H&interval=PT1H' \
 -H 'Authorization: Bearer YOUR_OAUTH_TOKEN' \
 -H 'Accept: application/json'

Notice the timeRange is in the URL. The interval can also go in the query params. If you need to group by agent, that’s also a query param: groupBy=agentId.

The 400 error happens because the API parser sees a body where it expects none, or it fails to map the body fields to the required OData filters. The timeFrame field in your JSON isn’t a valid parameter for this specific endpoint. It wants timeRange.

Also, check your OAuth token scopes. You need reporting:read at minimum. If you’re pulling this for WEM scheduling validation, make sure the token belongs to a user with admin or supervisor rights, otherwise you might get empty results even if the call succeeds.

One gotcha: the history data is sampled. If you query for a short interval like PT1H with a PT5M interval, you might hit rate limits or get truncated data. Stick to PT1H or PT4H intervals for daily reports. It saves memory and reduces the chance of timeout errors on the edge.

If you’re building this in code, use the SDK’s ReportingApi class. It handles the query string construction for you.

from platformclientv2 import ReportingApi
from platformclientv2.models import AgentStateHistoryQuery

api_instance = ReportingApi()
query = AgentStateHistoryQuery(
 time_range="PT24H",
 interval="PT1H",
 group_by=["agentId"]
)
result = api_instance.post_reporting_agents_states_history(body=query)

This avoids the manual URL encoding headaches.