CXone API client_credentials returning 400 despite valid scopes

I’ve spent hours trying to figure out why the CXone OAuth token endpoint rejects my client_credentials request with a 400 Bad Request, even though the documentation explicitly states this grant type should work for service-to-service authentication.

Background

I am building a Python backend service (FastAPI) that needs to programmatically fetch conversation history without user interaction. According to the CXone Developer Portal, the client_credentials grant is supported for API keys with sufficient permissions. I have generated an API Key in the CXone admin console and assigned it the analytics:reports:read scope.

Issue

When I execute the following Python code using requests, I receive a 400 error. The response body is empty, which provides zero debugging information.

import requests

url = "https://api.mycxone.com/api/v2/oauth/token"
payload = {
 "grant_type": "client_credentials",
 "client_id": "my_api_key_id",
 "client_secret": "my_api_key_secret"
}

response = requests.post(url, data=payload)
print(response.status_code)
print(response.text)

The documentation says: “To obtain an access token using client credentials, send a POST request to the token endpoint with your API key ID as the client_id and the secret as the client_secret.”

Troubleshooting

  1. I verified the API Key is active and not expired.
  2. I confirmed the client_id and client_secret are correct by using them in a Postman collection that works for other endpoints (though Postman uses authorization code flow for those).
  3. I tried adding Content-Type: application/x-www-form-urlencoded header explicitly, but it defaults to this anyway.
  4. The error persists across different API keys with varying scopes.

Is there a hidden requirement for the client_credentials flow in CXone that is not documented? Or is the endpoint path different for this grant type?