Fetch All Queues Across Multiple Divisions with the Genesys Cloud JavaScript SDK
What You Will Build
- A Node.js script that enumerates every queue in a Genesys Cloud organization, regardless of division boundaries.
- The solution uses the
@genesyscloud/purecloud-platform-client-v2SDK to handle pagination and division discovery automatically. - The implementation is written in JavaScript using modern async/await patterns and handles rate limiting and authentication lifecycle.
Prerequisites
- OAuth Client Type: Client Credentials flow (Service Account).
- Required Scopes:
queue:viewis required to read queue details. If you need to modify queues later, you would needqueue:write, but for this tutorial, read access is sufficient. - SDK Version:
@genesyscloud/purecloud-platform-client-v2version 135.0.0 or higher. - Runtime: Node.js version 18 or higher.
- Dependencies:
@genesyscloud/purecloud-platform-client-v2dotenv(for secure credential management)
Install the dependencies using npm:
npm install @genesyscloud/purecloud-platform-client-v2 dotenv
Authentication Setup
The Genesys Cloud Platform SDK handles the OAuth Client Credentials flow internally. You must configure the PlatformClient with your environment, client ID, and client secret. The SDK manages token caching and automatic refresh, so you do not need to implement manual token rotation logic.
Create a .env file in your project root to store sensitive credentials securely. Never commit these values to version control.
GENESYS_ENVIRONMENT=mypurecloud.com
GENESYS_CLIENT_ID=your_client_id_here
GENESYS_CLIENT_SECRET=your_client_secret_here
Initialize the client in your main module. The setEnvironment method determines the base URL (e.g., https://api.mypurecloud.com), and login initializes the OAuth token cache.
require('dotenv').config();
const { PlatformClient } = require('@genesyscloud/purecloud-platform-client-v2');
const platformClient = new PlatformClient();
async function initializeClient() {
try {
// Configure the environment based on the region
platformClient.setEnvironment(process.env.GENESYS_ENVIRONMENT);
// Log in using Client Credentials
await platformClient.login(
process.env.GENESYS_CLIENT_ID,
process.env.GENESYS_CLIENT_SECRET
);
console.log('Authentication successful.');
} catch (error) {
console.error('Authentication failed:', error.message);
process.exit(1);
}
}
Implementation
Step 1: Fetching Queues with Pagination
The Genesys Cloud API returns paginated results for list endpoints. The default page size is often 25 or 50 items, but you can request up to 1000. However, relying on a single large page size is risky if the total number of queues exceeds the limit or if the API changes. The robust approach is to iterate through pages until nextPageUri is null.
The endpoint for listing queues is GET /api/v2/queues. This endpoint supports the divisionId parameter. If you omit divisionId, the API returns queues from the “default” division only. To fetch queues from all divisions, you must first identify all divisions, then query queues for each division, or use a specific SDK helper if available. As of current SDK versions, there is no single “get all queues across all divisions” endpoint that handles division discovery internally. Therefore, we must implement a two-step process:
- List all divisions.
- List queues for each division.
First, let us define a function to fetch all divisions. The endpoint is GET /api/v2/divisions.
const { PlatformClient } = require('@genesyscloud/purecloud-platform-client-v2');
// Assume platformClient is already initialized and logged in
async function getAllDivisions(platformClient) {
const divisions = [];
let nextPageUri = null;
do {
try {
// The API call to list divisions
// If nextPageUri is null, it fetches the first page
const response = await platformClient.divisionApi.getDivisions({
pageSize: 1000 // Max recommended page size
}, nextPageUri);
if (response && response.entities) {
divisions.push(...response.entities);
}
// Update nextPageUri for the next iteration
// If null, the loop will terminate
nextPageUri = response.nextPageUri;
} catch (error) {
if (error.status === 429) {
console.warn('Rate limited on division fetch. Retrying in 5 seconds...');
await new Promise(resolve => setTimeout(resolve, 5000));
continue; // Retry the same page
}
console.error('Error fetching divisions:', error.message);
throw error;
}
} while (nextPageUri);
return divisions;
}
Step 2: Fetching Queues Per Division
Now that we have the list of divisions, we iterate through each division ID and fetch the queues. We must be careful with concurrency. If an organization has 50 divisions, firing 50 simultaneous API requests may trigger rate limits (429 Too Many Requests). A controlled concurrency pattern is recommended.
The endpoint is GET /api/v2/queues?divisionId={divisionId}.
async function getQueuesForDivision(platformClient, divisionId) {
const queues = [];
let nextPageUri = null;
do {
try {
// Fetch queues for a specific division
const response = await platformClient.queueApi.getQueues({
divisionId: divisionId,
pageSize: 1000
}, nextPageUri);
if (response && response.entities) {
queues.push(...response.entities);
}
nextPageUri = response.nextPageUri;
} catch (error) {
if (error.status === 404) {
// Division exists but has no queues, or division ID is invalid
console.warn(`No queues found or division ${divisionId} not accessible.`);
} else if (error.status === 429) {
console.warn(`Rate limited on queue fetch for division ${divisionId}. Retrying...`);
await new Promise(resolve => setTimeout(resolve, 2000));
continue;
} else {
console.error(`Error fetching queues for division ${divisionId}:`, error.message);
throw error;
}
}
} while (nextPageUri);
return queues;
}
Step 3: Orchestrating with Concurrency Control
To prevent overwhelming the API, we implement a simple concurrency limiter. This function processes an array of division IDs in batches.
async function fetchAllQueues(platformClient) {
console.log('Fetching all divisions...');
const divisions = await getAllDivisions(platformClient);
console.log(`Found ${divisions.length} divisions.`);
const allQueues = [];
const batchSize = 5; // Process 5 divisions concurrently
// Process divisions in batches
for (let i = 0; i < divisions.length; i += batchSize) {
const batch = divisions.slice(i, i + batchSize);
// Map each division in the batch to a promise
const promises = batch.map(async (division) => {
try {
const queues = await getQueuesForDivision(platformClient, division.id);
// Tag each queue with its division name for easier debugging
return queues.map(q => ({
...q,
divisionName: division.name,
divisionId: division.id
}));
} catch (error) {
console.error(`Failed to process division ${division.name}:`, error.message);
return [];
}
});
// Wait for the current batch to complete
const results = await Promise.all(promises);
// Flatten the array of arrays into a single array
results.forEach(queueBatch => {
allQueues.push(...queueBatch);
});
// Optional: Add a small delay between batches to be respectful to the API
if (i + batchSize < divisions.length) {
await new Promise(resolve => setTimeout(resolve, 500));
}
}
return allQueues;
}
Complete Working Example
Below is the full, copy-pasteable script. It includes the initialization, division discovery, queue fetching with pagination and rate-limit handling, and a final output summary.
require('dotenv').config();
const { PlatformClient } = require('@genesyscloud/purecloud-platform-client-v2');
// Initialize the Platform Client
const platformClient = new PlatformClient();
/**
* Authenticates the client using Client Credentials flow.
*/
async function initializeClient() {
try {
platformClient.setEnvironment(process.env.GENESYS_ENVIRONMENT);
await platformClient.login(
process.env.GENESYS_CLIENT_ID,
process.env.GENESYS_CLIENT_SECRET
);
console.log('Authentication successful.');
} catch (error) {
console.error('Authentication failed:', error.message);
process.exit(1);
}
}
/**
* Fetches all divisions with pagination.
* @param {PlatformClient} client
* @returns {Promise<Array>} List of division entities
*/
async function getAllDivisions(client) {
const divisions = [];
let nextPageUri = null;
do {
try {
const response = await client.divisionApi.getDivisions({
pageSize: 1000
}, nextPageUri);
if (response && response.entities) {
divisions.push(...response.entities);
}
nextPageUri = response.nextPageUri;
} catch (error) {
if (error.status === 429) {
console.warn('Rate limited on division fetch. Waiting 5 seconds...');
await new Promise(resolve => setTimeout(resolve, 5000));
continue;
}
console.error('Error fetching divisions:', error.message);
throw error;
}
} while (nextPageUri);
return divisions;
}
/**
* Fetches all queues for a specific division with pagination.
* @param {PlatformClient} client
* @param {string} divisionId
* @returns {Promise<Array>} List of queue entities
*/
async function getQueuesForDivision(client, divisionId) {
const queues = [];
let nextPageUri = null;
do {
try {
const response = await client.queueApi.getQueues({
divisionId: divisionId,
pageSize: 1000
}, nextPageUri);
if (response && response.entities) {
queues.push(...response.entities);
}
nextPageUri = response.nextPageUri;
} catch (error) {
if (error.status === 404) {
// Division has no queues or is inaccessible
console.log(`No queues found for division ID: ${divisionId}`);
} else if (error.status === 429) {
console.warn(`Rate limited on queue fetch for division ${divisionId}. Waiting 2 seconds...`);
await new Promise(resolve => setTimeout(resolve, 2000));
continue;
} else {
console.error(`Error fetching queues for division ${divisionId}:`, error.message);
throw error;
}
}
} while (nextPageUri);
return queues;
}
/**
* Main orchestration function to fetch all queues across all divisions.
*/
async function main() {
await initializeClient();
try {
console.log('Step 1: Discovering divisions...');
const divisions = await getAllDivisions(platformClient);
console.log(`Found ${divisions.length} divisions.`);
const allQueues = [];
const batchSize = 5; // Concurrency limit
console.log('Step 2: Fetching queues for each division...');
for (let i = 0; i < divisions.length; i += batchSize) {
const batch = divisions.slice(i, i + batchSize);
const promises = batch.map(async (division) => {
try {
const queues = await getQueuesForDivision(platformClient, division.id);
// Enrich queue data with division context
return queues.map(q => ({
...q,
_divisionName: division.name,
_divisionId: division.id
}));
} catch (error) {
console.error(`Failed to process division: ${division.name}`, error.message);
return [];
}
});
const results = await Promise.all(promises);
// Flatten results
results.forEach(queueBatch => {
allQueues.push(...queueBatch);
});
// Small delay between batches to respect API limits
if (i + batchSize < divisions.length) {
await new Promise(resolve => setTimeout(resolve, 500));
}
}
console.log(`Step 3: Complete. Total queues fetched: ${allQueues.length}`);
// Example: Print first 5 queues
console.log('\nSample Queues:');
allQueues.slice(0, 5).forEach(q => {
console.log(`- ${q.name} (Div: ${q._divisionName})`);
});
// You can now export this data, e.g., to JSON
// require('fs').writeFileSync('queues.json', JSON.stringify(allQueues, null, 2));
} catch (error) {
console.error('Fatal error in main execution:', error);
process.exit(1);
}
}
// Execute the script
main();
Common Errors & Debugging
Error: 401 Unauthorized
Cause: The OAuth token is expired, invalid, or the client credentials are incorrect.
Fix: Ensure your GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET in the .env file match the credentials created in the Genesys Cloud Admin Console. The SDK automatically refreshes tokens, but if the initial login fails, it will throw a 401. Check that the service account has not been disabled.
// Check error status in catch block
if (error.status === 401) {
console.error('Invalid credentials or expired token. Check .env file.');
}
Error: 403 Forbidden
Cause: The service account does not have the required queue:view scope, or the account does not have permission to view queues in the specific division (due to role-based access control).
Fix:
- Verify the OAuth Client in Genesys Cloud has the
queue:viewscope selected. - Ensure the service account has a role assigned that allows viewing queues in the target divisions. If you are using division-specific roles, the service account must be a member of those divisions or have a global role with queue visibility.
Error: 429 Too Many Requests
Cause: The API rate limit has been exceeded. This is common when iterating through many divisions quickly.
Fix: The provided code includes a retry mechanism with exponential backoff (simple delay). If you continue to hit 429s, increase the batchSize reduction or increase the delay between batches. Monitor the X-RateLimit-Remaining header in raw API calls if you need fine-grained control, though the SDK abstracts this away.
// Inside the catch block
if (error.status === 429) {
// Wait before retrying
await new Promise(resolve => setTimeout(resolve, 2000));
continue; // Retry the same operation
}
Error: TypeError: Cannot read properties of null (reading ‘entities’)
Cause: The API response is null or undefined, often due to a network error or an unexpected API change.
Fix: Always check for the existence of response and response.entities before pushing to the array. The code above includes these checks:
if (response && response.entities) {
queues.push(...response.entities);
}