CXone client_credentials 401 with valid keys

Having some issues getting my configuration to work…

Node 20
NICE CXone API v2

Snippet:

const res = await axios.post('https://api.nicecxone.com/auth/oauth/token', {
 grant_type: 'client_credentials',
 client_id: process.env.CXONE_CLIENT_ID,
 client_secret: process.env.CXONE_CLIENT_SECRET
});

Getting 401 Unauthorized.

  • Headers set to application/x-www-form-urlencoded
  • Keys verified in NICE portal
  • No proxy involved

Curl works. Axios fails. Why?

The root of the issue is likely how the payload is serialized. The OAuth endpoint expects form-urlencoded data, but Axios defaults to JSON. Sending JSON triggers a 401 because the server cannot parse the credentials.

  1. Use qs library or manual serialization.
  2. Ensure Content-Type is explicitly set.
  3. Verify client_credentials grant type.

Correct Node.js implementation:

const axios = require('axios');
const qs = require('qs');

const authData = qs.stringify({
 grant_type: 'client_credentials',
 client_id: process.env.CXONE_CLIENT_ID,
 client_secret: process.env.CXONE_CLIENT_SECRET
});

const response = await axios.post('https://api.nicecxone.com/auth/oauth/token', authData, {
 headers: {
 'Content-Type': 'application/x-www-form-urlencoded'
 }
});
Component Requirement
Library qs or URLSearchParams
Header application/x-www-form-urlencoded
Scope Implicit for client credentials

Audit logging requires valid tokens. If this fails, check your application permissions in the portal. Ensure the app has the necessary oauth_auth scope enabled.

I’d recommend looking at at the payload serialization format. The suggestion above correctly identifies the JSON vs form-urlencoded mismatch, but there is a secondary risk in production environments regarding token caching and rate limits.

My config is not working… Node 20 NICE CXone API v2 Snippet: const res = await axios.post(‘https://api.nicecxone.com/auth/oauth/token’, { grant_type: ‘client_credentials’, client_id: process.env.CXONE_CLIENT_ID, client_secret: process.env.CXONE_CLIENT_SECRET }); Getting 401 Unauthorized.

If you fix the serialization using URLSearchParams, you will get a 200. However, if this code runs inside a k6 loop or a high-frequency webhook handler, you will hit the OAuth endpoint’s rate limit immediately. The client_credentials grant returns a token valid for 2 hours. Do not request a new token on every API call.

Implement a simple TTL cache for the access token.

let cachedToken = null;
let tokenExpiry = 0;

async function getAccessToken() {
 if (cachedToken && Date.now() < tokenExpiry) {
 return cachedToken;
 }

 const params = new URLSearchParams({
 grant_type: 'client_credentials',
 client_id: process.env.CXONE_CLIENT_ID,
 client_secret: process.env.CXONE_CLIENT_SECRET
 });

 const res = await axios.post('https://api.nicecxone.com/auth/oauth/token', params, {
 headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
 });

 cachedToken = res.data.access_token;
 // Subtract 5 mins for safety buffer
 tokenExpiry = Date.now() + (res.data.expires_in - 300) * 1000;
 
 return cachedToken;
}

Ignoring this pattern causes 429 Too Many Requests errors that are harder to debug than 401s.

This looks like a serialization mismatch.

  • Use application/x-www-form-urlencoded headers.
  • Encode payload as grant_type=client_credentials&client_id=...&client_secret=....