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 Genesys Cloud 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. If this is a Genesys Cloud environment, the endpoint should be https://api.mypurecloud.com/oauth2/token. If it’s strictly NICE CXone, then api.mynice.com is correct, but the grant type configuration is still the first thing to verify.
Here’s the corrected curl command assuming you’re on the Genesys Cloud platform (adjust the host if you’re on CXone):
curl -X POST 'https://api.mypurecloud.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 Kotlin SDK, you don’t really need to do this manually. The PureCloudPlatformClientV2 handles the token exchange for you. Just initialize it with the credentials:
val config = PureCloudPlatformClientV2Configuration()
config.clientId = "YOUR_CLIENT_ID"
config.clientSecret = "YOUR_CLIENT_SECRET"
config.baseUri = "https://api.mypurecloud.com"
val platformClient = PureCloudPlatformClientV2(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.