CXone client_credentials grant failing with 403 on custom app

I’m trying to set up a background service that authenticates to the CXone API using the client_credentials grant. The goal is to fetch queue stats without tying up an agent session.

I have the client ID and secret from the application settings. I’m posting to https://{{org_name}}.api.nice.incontact.com/oauth2/token with application/x-www-form-urlencoded body. Here is the payload structure I’m sending:

grant_type=client_credentials
&client_id=my_app_id
&client_secret=my_secret

The response comes back as a 403 Forbidden with {"error": "invalid_grant"}. I’ve double checked the secret by copying it directly from the console. I even tried URL encoding the secret just in case, but no luck.

Is there a specific scope I need to request in the body for this grant type? I left the scope parameter out entirely since the docs for client_credentials are sparse compared to authorization_code.

Also, does the application type need to be set to something specific other than Custom? I’m building this in Python using requests.

The 403 usually means the scope isn’t quite right or the app permissions are missing that specific resource. You’ve got the endpoint correct for CXone, which is good. The client_credentials flow is strict about what it can access. If you’re trying to get queue stats, you need the analytics:call-quality:view or routing:queue:view scope depending on exactly which API you’re hitting next.

Make sure your app has the Read-only permission for the Analytics and Routing resources in the admin console. Just having the ID and secret isn’t enough if the app itself doesn’t have the right permissions assigned. Also, check the scope parameter in your POST body. It’s easy to forget that part when copying examples.

Here’s how the full request should look. I use Postman to test this first before putting it in code.

curl -X POST "https://{{org_name}}.api.nice.incontact.com/oauth2/token" \
 -H "Content-Type: application/x-www-form-urlencoded" \
 -d "grant_type=client_credentials&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&scope=analytics:call-quality:view routing:queue:view"

If you still get a 403 after fixing the scope, check the token response. The scope field in the JSON response will tell you exactly what was granted. If it’s empty or missing the ones you asked for, the app permissions in the UI are the culprit. I’ve seen this happen when someone creates an app but forgets to save the permission changes before testing.

Also, make sure the client secret hasn’t been regenerated since you last tested. It’s a small thing but it breaks things silently. Once the token comes back, use it in the Authorization: Bearer <token> header for your queue stats call. The stats endpoint is /api/v2/analytics/queues/summary usually.

Let me know if the scope in the token response matches what you sent.