CXone client_credentials grant failing with 400 Bad Request despite valid base64

Stuck on a simple auth flow for a custom DFO channel integration. Trying to get a bearer token via client_credentials grant type against our CXone org. The endpoint is https://api.mynice.com/oauth/token.

Here’s the curl command I’m running:

curl -X POST https://api.mynice.com/oauth/token \
 -H 'Content-Type: application/x-www-form-urlencoded' \
 -H 'Authorization: Basic <base64(client_id:client_secret)>' \
 -d 'grant_type=client_credentials'

Getting a 400 Bad Request with this JSON payload:

{
 "error": "invalid_grant",
 "error_description": "Bad credentials"
}

I’ve verified the client ID and secret in the Developer Portal. The app has admin:api and user:read scopes assigned. I even regenerated the secret just in case it was stale. The base64 encoding looks correct when I decode it manually.

Is there a specific scope requirement for the token endpoint itself that I’m missing? Or is the Basic auth header format different for CXone compared to standard OAuth2? I’m used to passing client_id and client_secret in the body, but the docs say Basic auth is preferred for security. Tried both ways. Same error.

Running this from a Linux box. cURL version 7.81.0. No proxy issues.