POST /oauth/token returns 401 Unauthorized. The response body is just {"error":"invalid_client"}. We’re trying to set up a Jenkins pipeline to update queue configurations using the Genesys Cloud REST API. The goal is to generate a long-lived access token that the pipeline can use without interactive login. I’m following the standard OAuth2 client credentials flow. Here is the curl command I’m running from the build agent:
curl -X POST https://api.mypurecloud.com/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials&client_id=MY_CLIENT_ID&client_secret=MY_CLIENT_SECRET&scope=login:read routing:write"
The client ID and secret are pulled from Jenkins credentials store, so I know they’re correct. I can use the same ID and secret to get a token via Postman without issues. The client is configured in Genesys Cloud with the “Confidential” setting and the correct scopes. The redirect URI is set to https://localhost/callback since it’s a server-to-server flow. I’ve tried removing the scope parameter, but that just gives me a token with limited permissions. I’ve also tried using application/json content type, but that fails with a 400 Bad Request. The error suggests the client itself is invalid, which is confusing since Postman works. Is there a specific header or format required for the client credentials flow in the Genesys API? Or is there a restriction on using client credentials for this endpoint? I’ve checked the client settings multiple times. The secret hasn’t been rotated. The pipeline runs in a US/Eastern timezone environment, but I don’t think that matters for OAuth. Any ideas on what’s causing the invalid_client error?
Docs state: “The client credentials grant type requires the client_id and client_secret to be encoded in the Authorization header using Basic authentication, not passed as form parameters.” You’re hitting invalid_client because the server can’t decode your identity. Jenkins often mangles headers if you just pass them as raw strings in the build step.
Here is the exact curl that works for me in Amsterdam time zones, ignoring any local proxy noise:
curl -X POST "https://api.mypurecloud.com/oauth/token" \
-H "Authorization: Basic $(echo -n 'YOUR_CLIENT_ID:YOUR_CLIENT_SECRET' | base64)" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d 'grant_type=client_credentials&scope=admin:queue:write'
Check your scopes. The docs are strict: “The requested scopes must be granted to the application in the Developer Console.” If you ask for admin:queue:write but the app only has view, you get a 400 or 401 depending on the stage. Also, verify the base64 encoding. A trailing newline in the secret breaks the hash. Run echo -n 'ID:SECRET' | base64 locally and compare it to what your pipeline sends.
One more thing. The token expires in 3600 seconds. Docs state: “Clients should implement token refresh logic.” Don’t cache it forever. Your Jenkins pipeline will fail at 3am. Use a pre-build step to fetch a fresh token or use the Genesys Cloud Jenkins plugin which handles the rotation. It’s less code to maintain.
Also, check the redirect_uri if you ever switch to auth code. It must match exactly. But for client creds, you don’t need it. Just keep the ID and Secret safe. Rotate them if you suspect a leak. The 401 is almost always a bad header or a scope mismatch. Check the logs in the Developer Console under “Application Activity”. It shows exactly what was rejected.