Setting up a new service account for our custom DFO channel integration. I’m trying to grab a token using the client_credentials grant type, but I keep hitting a 401 Unauthorized. The credentials are definitely correct since I generated them fresh in the admin portal.
Here’s the curl command I’m running:
curl -X POST 'https://api.mynice.com/oauth2/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=client_credentials&client_id=MY_CLIENT_ID&client_secret=MY_CLIENT_SECRET'
The response body is just {"error":"invalid_client","error_description":"Invalid client credentials"}.
I’ve double-checked for trailing spaces in the secret. I also tried encoding the client_id and secret in the Authorization header as Base64, but that threw a 400 Bad Request saying the grant type is missing. The docs say client_credentials doesn’t need a redirect URI, which makes sense for a backend service.
Is there a specific scope I need to request in the body, or is this endpoint just being finicky about the content type? I’ve used this flow before with other integrations without issue. Checking the network trace, the request looks identical to a working call from our staging env.
Any ideas on what’s causing the invalid_client error when the creds are valid?
Are you sure the service account actually has the client_credentials grant type enabled? That’s the usual culprit here. The default setting is often authorization_code, and if you haven’t explicitly toggled that switch in the developer portal, the token endpoint will reject the request with a 401 before it even checks your client secret.
You’ll need to go into the CXone admin portal, navigate to Admin > Security > OAuth Client Applications, find your service account, and edit it. Look for the Allowed Grant Types section. Make sure client_credentials is checked. If it’s not, that’s your problem.
Once that’s sorted, your curl command looks mostly right, but watch out for the URL. You mentioned api.mynice.com. Double-check that this is the correct endpoint for your CXone environment. It’s possible there are regional variations or custom domains in play. If it’s not correct, consult the CXone documentation for the proper token endpoint.
Here’s the corrected curl command assuming api.mynice.com is correct:
curl -X POST 'https://api.mynice.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'
If you’re using the CXone SDK, you don’t really need to do this manually. The SDK handles the token exchange for you. Just initialize it with the credentials:
val config = CXonePlatformClientConfiguration()
config.clientId = "YOUR_CLIENT_ID"
config.clientSecret = "YOUR_CLIENT_SECRET"
config.baseUri = "https://api.mynice.com"
val platformClient = CXonePlatformClient(config)
// The SDK will auto-fetch the token when you make your first API call
The SDK manages the refresh and expiry behind the scenes. It’s much cleaner than managing curl calls in a pipeline. Double-check the grant type setting first though. It’s usually that simple. If you’re still facing issues, verify the client application’s active status and that there are no associated rate limits or restrictions configured within the CXone portal.