Python requests OAuth2 Client Credentials returning 401

Stuck on a 401 Unauthorized response when attempting to fetch an access token via the Genesys Cloud OAuth endpoint using Python requests. The credentials are verified as correct in the admin console.

import requests

url = "https://api.mypurecloud.com/oauth/token"
auth = ('my_client_id', 'my_client_secret')
headers = {'Content-Type': 'application/x-www-form-urlencoded'}

res = requests.post(url, auth=auth, headers=headers, data={'grant_type': 'client_credentials'})
print(res.status_code) # Returns 401

Why does this setting fail to return a valid access token?

Make sure you use data instead of auth for the client credentials. The 401 comes from sending the secret in Basic Auth header when the endpoint expects form-encoded body.

{
 "grant_type": "client_credentials",
 "client_id": "my_client_id",
 "client_secret": "my_client_secret"
}

check your payload structure and endpoint configuration. while the suggestion above to use form-encoded data is technically correct for the application/x-www-form-urlencoded content type, sending the client secret in the body is often blocked by stricter security policies or legacy gateway configurations in certain regions. a more robust approach, especially when integrating with architect data actions or python scripts that need to handle token refresh logic, is to use basic authentication for the credentials while keeping the grant type in the body. this matches the standard oauth 2.0 client credentials flow expected by the purecloud sdk internals.

here is the adjusted python snippet using requests.auth for the header and data for the grant type. this separation ensures the secret is not exposed in the raw body logs and aligns with how the purecloudplatformclientv2 sdk handles authentication under the hood.

import requests
import base64

url = "https://api.mypurecloud.com/oauth/token"
client_id = "my_client_id"
client_secret = "my_client_secret"

# encode credentials for basic auth header
credentials = f"{client_id}:{client_secret}"
encoded_credentials = base64.b64encode(credentials.encode('utf-8')).decode('utf-8')

headers = {
 "Content-Type": "application/x-www-form-urlencoded",
 "Authorization": f"Basic {encoded_credentials}"
}

# only grant_type in the body
data = {
 "grant_type": "client_credentials",
 "scope": "admin:oauth" # specify minimal required scopes
}

response = requests.post(url, headers=headers, data=data)
print(response.json())

if you are still seeing a 401, verify the oauth client’s scopes in the admin console. the client must have the admin:oauth scope enabled to issue tokens. also, ensure you are not hitting rate limits from previous failed attempts, which can cause temporary 401s that look like auth failures. check the Retry-After header if present.