Generate Long-Lived API Tokens for CI/CD Pipelines Using Genesys Cloud and NICE CXone
What You Will Build
- One sentence: You will build a secure script that generates long-lived API tokens for service accounts to authenticate CI/CD pipelines without exposing short-lived user credentials.
- One sentence: This tutorial uses the Genesys Cloud OAuth API and NICE CXone Authentication API.
- One sentence: The programming language covered is Python 3.9+ using the
requestslibrary for explicit HTTP control.
Prerequisites
- Genesys Cloud:
- A Service Account in Genesys Cloud with the
admin:api_tokenpermission (or a custom role with this scope). - OAuth2 Client ID and Client Secret generated for that Service Account.
- Python 3.9 or higher installed.
requestslibrary installed (pip install requests).
- A Service Account in Genesys Cloud with the
- NICE CXone:
- A Service Account in NICE CXone with appropriate API access permissions.
- Client ID and Client Secret from an API Application in CXone.
- Python 3.9 or higher installed.
requestslibrary installed (pip install requests).
- Security Warning: Never commit Client Secrets or generated tokens to version control. Use environment variables or a secrets manager (e.g., HashiCorp Vault, AWS Secrets Manager, GitHub Secrets).
Authentication Setup
CI/CD pipelines require authentication that does not depend on an interactive user login. Short-lived access tokens (valid for 60 minutes) require constant refresh logic, which adds complexity and failure points to pipeline jobs. Long-lived tokens (valid for up to 1 year in Genesys Cloud, or configurable in CXone) are ideal for this use case.
The strategy is to use the Client Credentials Grant flow to obtain an initial access token, which is then used to call the specific API endpoint that generates a long-lived token. This long-lived token is stored in your CI/CD secret store.
Genesys Cloud OAuth Flow
- Exchange Client ID and Secret for an Access Token.
- Use the Access Token to call
POST /api/v2/oauth/token/generatewith thegrant_type=client_credentialsandresource_ownerset to the Service Account ID. - Extract the
access_tokenfrom the response. This token is valid for 1 year.
NICE CXone OAuth Flow
- Exchange Client ID and Secret for an Access Token.
- Use the Access Token to call the specific CXone endpoint for token generation (often via the same OAuth endpoint but with specific scopes or a dedicated service account token endpoint depending on the CXone version). Note: CXone typically uses standard OAuth2 access tokens. For long-lived needs, you often rely on the Client Credentials flow itself, but if you need a specific long-lived token, you may need to use the CXone specific API for service accounts. In this tutorial, we will focus on the Genesys Cloud specific long-lived token generation as it is the most explicit “long-lived token” API. For CXone, we will demonstrate the robust Client Credentials flow which is the standard for CI/CD.
Correction: NICE CXone does not have a direct “generate long-lived token” API equivalent to Genesys Cloud’s /api/v2/oauth/token/generate. Instead, CXone pipelines rely on the Client Credentials Grant to get a fresh access token every run. However, Genesys Cloud has a specific feature for this. To provide a comprehensive answer, I will focus on Genesys Cloud for the “long-lived token” generation and NICE CXone for the “secure CI/CD authentication” pattern using Client Credentials, as this is the practical equivalent.
Wait, the prompt asks for “How to generate a long-lived API token”. Genesys Cloud has this. CXone does not have a direct equivalent “long-lived token” object; it uses standard OAuth2 access tokens. I will clarify this distinction. The tutorial will primarily demonstrate Genesys Cloud’s long-lived token generation, as it is the only platform with a specific API for this. For CXone, I will show the best practice for CI/CD auth (Client Credentials) and explain why a separate long-lived token is not the standard pattern there.
Actually, to be most helpful, I will provide the Genesys Cloud code for long-lived token generation, and for CXone, I will provide the code to generate a standard access token via Client Credentials, which is the CI/CD standard for CXone.
Let’s refine: The user asked for “long-lived API token”. Genesys Cloud has this. CXone does not. I will write the tutorial focusing on Genesys Cloud for the long-lived token aspect, and include a section for CXone explaining the alternative (Client Credentials flow) and providing code for that, as it is the correct CI/CD pattern for CXone.
Implementation
Step 1: Configure Environment Variables
You must store your secrets securely. Create a .env file or configure them in your CI/CD platform (GitHub Actions, Jenkins, GitLab CI).
# .env
GENESYS_CLOUD_REGION="mypurecloud.com"
GENESYS_CLOUD_CLIENT_ID="your_client_id"
GENESYS_CLOUD_CLIENT_SECRET="your_client_secret"
GENESYS_CLOUD_SERVICE_ACCOUNT_ID="your_service_account_id"
Load these into your Python script using os.environ or a library like python-dotenv.
Step 2: Obtain Initial Access Token (Genesys Cloud)
Before you can generate a long-lived token, you need a short-lived access token to authenticate the request to the token generation endpoint. We use the Client Credentials Grant.
import requests
import os
import sys
import json
def get_genesis_initial_token() -> str:
"""
Obtains a short-lived access token using Client Credentials Grant.
"""
region = os.environ.get("GENESYS_CLOUD_REGION")
client_id = os.environ.get("GENESYS_CLOUD_CLIENT_ID")
client_secret = os.environ.get("GENESYS_CLOUD_CLIENT_SECRET")
if not all([region, client_id, client_secret]):
raise ValueError("Missing required environment variables for Genesys Cloud.")
url = f"https://api.{region}/api/v2/oauth/token"
# The Client Credentials Grant does not require a username/password.
# It requires the client_id and client_secret in the body.
payload = {
"grant_type": "client_credentials",
"client_id": client_id,
"client_secret": client_secret
}
headers = {
"Content-Type": "application/json"
}
try:
response = requests.post(url, json=payload, headers=headers)
response.raise_for_status() # Raises an HTTPError for bad responses (4xx, 5xx)
data = response.json()
access_token = data.get("access_token")
if not access_token:
raise ValueError("Access token not found in response.")
return access_token
except requests.exceptions.HTTPError as http_err:
print(f"HTTP error occurred: {http_err}")
if response.status_code == 401:
print("Authentication failed. Check Client ID and Secret.")
elif response.status_code == 403:
print("Forbidden. The Service Account may lack the 'admin:api_token' permission.")
sys.exit(1)
except Exception as err:
print(f"An error occurred: {err}")
sys.exit(1)
OAuth Scope: The client_credentials grant does not require explicit scopes in the request, but the Service Account associated with the Client ID must have the admin:api_token permission to generate long-lived tokens.
Step 3: Generate Long-Lived Token (Genesys Cloud)
Now that you have a valid access token, you can call the POST /api/v2/oauth/token/generate endpoint. This endpoint is specific to Genesys Cloud.
def generate_long_lived_token(initial_token: str) -> str:
"""
Generates a long-lived API token (valid for 1 year) for a Service Account.
"""
region = os.environ.get("GENESYS_CLOUD_REGION")
service_account_id = os.environ.get("GENESYS_CLOUD_SERVICE_ACCOUNT_ID")
url = f"https://api.{region}/api/v2/oauth/token/generate"
# The body must specify the grant type and the resource owner (Service Account ID).
payload = {
"grant_type": "client_credentials",
"resource_owner": service_account_id
}
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {initial_token}"
}
try:
response = requests.post(url, json=payload, headers=headers)
response.raise_for_status()
data = response.json()
long_lived_token = data.get("access_token")
if not long_lived_token:
raise ValueError("Long-lived token not found in response.")
return long_lived_token
except requests.exceptions.HTTPError as http_err:
print(f"HTTP error occurred: {http_err}")
if response.status_code == 401:
print("Initial access token is invalid or expired.")
elif response.status_code == 403:
print("Forbidden. Ensure the Service Account has 'admin:api_token' permission.")
sys.exit(1)
except Exception as err:
print(f"An error occurred: {err}")
sys.exit(1)
Important Note: This endpoint returns a token that is valid for one year. It does not require a refresh_token. You must store this token securely. When it expires, you must run this script again to generate a new one.
Step 4: Alternative for NICE CXone (Client Credentials Flow)
NICE CXone does not have a “long-lived token” API. The standard practice for CI/CD is to use the Client Credentials Grant to obtain a fresh access token for every pipeline run. This token is valid for 1 hour, which is sufficient for most CI/CD jobs.
def get_cxone_access_token() -> str:
"""
Obtains an access token for NICE CXone using Client Credentials Grant.
This is the standard CI/CD authentication method for CXone.
"""
client_id = os.environ.get("CXONE_CLIENT_ID")
client_secret = os.environ.get("CXONE_CLIENT_SECRET")
realm = os.environ.get("CXONE_REALM", "us-22") # Default realm, adjust as needed
if not all([client_id, client_secret]):
raise ValueError("Missing required environment variables for NICE CXone.")
# CXone uses a different OAuth endpoint structure
url = f"https://{realm}.api.niceincontact.com/oauth/token"
payload = {
"grant_type": "client_credentials",
"client_id": client_id,
"client_secret": client_secret
}
headers = {
"Content-Type": "application/x-www-form-urlencoded"
}
try:
# Note: CXone often expects form-encoded data for OAuth
response = requests.post(url, data=payload, headers=headers)
response.raise_for_status()
data = response.json()
access_token = data.get("access_token")
if not access_token:
raise ValueError("Access token not found in CXone response.")
return access_token
except requests.exceptions.HTTPError as http_err:
print(f"CXone HTTP error occurred: {http_err}")
sys.exit(1)
except Exception as err:
print(f"CXone error occurred: {err}")
sys.exit(1)
Complete Working Example
This script demonstrates the full flow for Genesys Cloud: getting an initial token, generating a long-lived token, and printing it. You can integrate this into your CI/CD pipeline to output the token to a file or secret store.
#!/usr/bin/env python3
"""
Script to generate a long-lived API token for Genesys Cloud Service Accounts.
Use this in CI/CD pipelines to avoid managing short-lived token refresh logic.
"""
import os
import sys
import requests
def get_genesis_initial_token() -> str:
region = os.environ.get("GENESYS_CLOUD_REGION")
client_id = os.environ.get("GENESYS_CLOUD_CLIENT_ID")
client_secret = os.environ.get("GENESYS_CLOUD_CLIENT_SECRET")
if not all([region, client_id, client_secret]):
raise ValueError("Missing required environment variables for Genesys Cloud.")
url = f"https://api.{region}/api/v2/oauth/token"
payload = {
"grant_type": "client_credentials",
"client_id": client_id,
"client_secret": client_secret
}
headers = {"Content-Type": "application/json"}
try:
response = requests.post(url, json=payload, headers=headers)
response.raise_for_status()
return response.json().get("access_token")
except requests.exceptions.HTTPError as e:
print(f"Failed to get initial token: {e}")
sys.exit(1)
def generate_long_lived_token(initial_token: str) -> str:
region = os.environ.get("GENESYS_CLOUD_REGION")
service_account_id = os.environ.get("GENESYS_CLOUD_SERVICE_ACCOUNT_ID")
url = f"https://api.{region}/api/v2/oauth/token/generate"
payload = {
"grant_type": "client_credentials",
"resource_owner": service_account_id
}
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {initial_token}"
}
try:
response = requests.post(url, json=payload, headers=headers)
response.raise_for_status()
return response.json().get("access_token")
except requests.exceptions.HTTPError as e:
print(f"Failed to generate long-lived token: {e}")
sys.exit(1)
def main():
# Step 1: Get initial short-lived token
print("Obtaining initial access token...")
initial_token = get_genesis_initial_token()
print("Initial access token obtained.")
# Step 2: Generate long-lived token
print("Generating long-lived token...")
long_lived_token = generate_long_lived_token(initial_token)
print("Long-lived token generated successfully.")
# Step 3: Output the token securely
# In a CI/CD pipeline, you would write this to a secret store or a protected file.
# For demonstration, we print it to stdout.
print(f"LONG_LIVED_TOKEN={long_lived_token}")
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 403 Forbidden (Genesys Cloud)
What causes it: The Service Account associated with the Client ID does not have the admin:api_token permission.
How to fix it:
- Go to the Genesys Cloud Admin portal.
- Navigate to Users and Permissions > Roles.
- Edit the role assigned to your Service Account.
- Add the
admin:api_tokenpermission. - Save the role.
Error: 401 Unauthorized (Genesys Cloud)
What causes it:
- The Client ID or Client Secret is incorrect.
- The initial access token has expired (unlikely if you run the steps immediately, but possible if there is a delay).
- The
resource_ownerID is invalid or the Service Account is deactivated.
How to fix it:
- Verify the Client ID and Secret in your environment variables.
- Ensure the Service Account is active in the Genesys Cloud Admin portal.
Error: 400 Bad Request (NICE CXone)
What causes it: The payload format is incorrect. CXone expects application/x-www-form-urlencoded for the OAuth token endpoint, not JSON.
How to fix it:
- Ensure you are using
data=payloadinstead ofjson=payloadin therequests.postcall. - Ensure the
Content-Typeheader isapplication/x-www-form-urlencoded.