Fetch All Queues Across Multiple Divisions Using Genesys Cloud Platform SDK for JavaScript

Fetch All Queues Across Multiple Divisions Using Genesys Cloud Platform SDK for JavaScript

What You Will Build

  • One sentence: This tutorial builds a Node.js script that retrieves every queue definition across all accessible divisions in a Genesys Cloud organization.
  • One sentence: It uses the Genesys Cloud Platform SDK for JavaScript (@genesyscloud/genesyscloud-platform-client-v2).
  • One sentence: The code demonstrates pagination, division iteration, and robust error handling for production environments.

Prerequisites

  • OAuth client type: Confidential Client (Client Credentials Grant).
  • Required OAuth scopes: queue:read (to view queues) and optionally user:read or division:read if you need to map division names, though the SDK handles division discovery automatically.
  • SDK version: @genesyscloud/genesyscloud-platform-client-v2 v10.0.0 or later.
  • Runtime: Node.js 16+ (LTS recommended).
  • External dependencies: None beyond the SDK and standard Node.js libraries.

Authentication Setup

The Genesys Cloud Platform SDK handles OAuth 2.0 Client Credentials flow internally. You must configure the PureCloudPlatformClientV2 instance with your client ID, client secret, and environment.

The SDK caches tokens and automatically refreshes them when they expire. You do not need to implement manual token refresh logic unless you are building a custom token provider.

// auth-setup.js
const { PureCloudPlatformClientV2 } = require('@genesyscloud/genesyscloud-platform-client-v2');

// Create the platform client instance
const platformClient = new PureCloudPlatformClientV2();

// Configure authentication
platformClient.authApi.clientCredentialsLogin(
  'YOUR_CLIENT_ID',    // Replace with your Client ID
  'YOUR_CLIENT_SECRET' // Replace with your Client Secret
);

// Optional: Set the environment explicitly if not using the default (mypurecloud.com)
// platformClient.setEnvironment('mypurecloud.ie'); 

// Verify authentication status
platformClient.authApi.getAccessToken()
  .then(token => {
    if (!token) {
      throw new Error('Authentication failed: No access token received.');
    }
    console.log('Successfully authenticated. Token expires at:', token.expires_in);
  })
  .catch(err => {
    console.error('Authentication error:', err.message);
    process.exit(1);
  });

OAuth Scopes Note: Ensure your OAuth client in the Genesys Cloud Admin UI has the queue:read scope assigned. Without this scope, the API will return a 403 Forbidden error.

Implementation

Step 1: Discover All Accessible Divisions

Queues in Genesys Cloud are partitioned by divisions. To fetch “all” queues, you cannot simply call the queue endpoint without a division context, as the default behavior may only return queues in the default division or require explicit division filtering.

The most robust approach is to first retrieve all divisions the authenticated user/client has access to, then iterate through each division to fetch queues.

// discover-divisions.js
const { PureCloudPlatformClientV2 } = require('@genesyscloud/genesyscloud-platform-client-v2');

const platformClient = new PureCloudPlatformClientV2();

// Assume auth is configured elsewhere
async function getAllDivisions() {
  const divisionsApi = platformClient.DivisionsApi;
  let allDivisions = [];
  let hasNextPage = true;
  let pageNumber = 1;

  console.log('Fetching divisions...');

  while (hasNextPage) {
    try {
      // Fetch divisions with pagination
      const response = await divisionsApi.getDivisions({
        expand: 'divisions', // Ensure full division details are returned
        pageNumber: pageNumber,
        pageSize: 250       // Max page size for divisions is typically 250
      });

      if (response.entities && response.entities.length > 0) {
        allDivisions = [...allDivisions, ...response.entities];
      }

      // Check if there are more pages
      hasNextPage = response.nextPageUri !== null && response.nextPageUri !== undefined;
      pageNumber++;

    } catch (error) {
      if (error.code === 429) {
        console.warn('Rate limited on divisions API. Retrying in 5 seconds...');
        await new Promise(resolve => setTimeout(resolve, 5000));
        continue; // Retry the same page
      }
      throw error;
    }
  }

  console.log(`Found ${allDivisions.length} divisions.`);
  return allDivisions;
}

module.exports = { getAllDivisions };

Why this step is critical: The getQueues endpoint accepts a divisionId parameter. If you omit it, the API behavior can vary based on organization settings. By explicitly iterating through divisions, you guarantee completeness and avoid missing queues in non-default divisions.

Step 2: Fetch Queues Per Division with Pagination

Now that you have a list of divisions, you must query the Queues API for each division. The getQueues endpoint supports pagination. You must handle the nextPageUri to ensure you retrieve all queues, especially in large organizations with hundreds of queues.

// fetch-queues-per-division.js
const { PureCloudPlatformClientV2 } = require('@genesyscloud/genesyscloud-platform-client-v2');

const platformClient = new PureCloudPlatformClientV2();

async function fetchQueuesForDivision(divisionId) {
  const queuesApi = platformClient.QueuesApi;
  let allQueues = [];
  let nextPageUri = null;
  let loopCount = 0;
  const maxLoops = 100; // Safety break to prevent infinite loops in case of API bugs

  console.log(`Fetching queues for division: ${divisionId}...`);

  do {
    try {
      let response;

      if (nextPageUri) {
        // Use the nextPageUri for subsequent calls
        response = await queuesApi.getQueuesWithHttpInfo({
          divisionId: divisionId,
          nextPageUri: nextPageUri
        });
      } else {
        // Initial call
        response = await queuesApi.getQueues({
          divisionId: divisionId,
          pageSize: 250 // Max page size for queues
        });
      }

      if (response.body.entities && response.body.entities.length > 0) {
        allQueues = [...allQueues, ...response.body.entities];
      }

      // Update nextPageUri for the next iteration
      nextPageUri = response.body.nextPageUri;
      loopCount++;

      // Small delay to be respectful of rate limits across multiple divisions
      await new Promise(resolve => setTimeout(resolve, 100));

    } catch (error) {
      if (error.code === 429) {
        console.warn(`Rate limited for division ${divisionId}. Retrying in 5 seconds...`);
        await new Promise(resolve => setTimeout(resolve, 5000));
        continue; // Retry the same page
      }
      if (error.code === 404) {
        console.warn(`Division ${divisionId} not found or no access. Skipping.`);
        break;
      }
      throw error;
    }

  } while (nextPageUri && loopCount < maxLoops);

  console.log(`Retrieved ${allQueues.length} queues for division ${divisionId}.`);
  return allQueues;
}

module.exports = { fetchQueuesForDivision };

Key Parameters Explained:

  • divisionId: The UUID of the division. This is mandatory for scoped queries.
  • pageSize: Setting this to 250 reduces the number of HTTP requests required.
  • nextPageUri: The SDK uses this URI to fetch the next page. Do not manually construct this URI; use the one returned in the response body.

Step 3: Aggregate and Process Results

The final step is to orchestrate the division discovery and queue fetching. You should aggregate all queues into a single array and handle any errors gracefully so that one failing division does not stop the entire process.

// aggregate-queues.js
const { getAllDivisions } = require('./discover-divisions');
const { fetchQueuesForDivision } = require('./fetch-queues-per-division');

async function fetchAllQueues() {
  let allQueues = [];
  let errors = [];

  try {
    // Step 1: Get all divisions
    const divisions = await getAllDivisions();

    // Step 2: Fetch queues for each division
    // Using Promise.allSettled to ensure all divisions are processed even if some fail
    const results = await Promise.allSettled(
      divisions.map(division => fetchQueuesForDivision(division.id))
    );

    // Step 3: Aggregate results
    results.forEach((result, index) => {
      if (result.status === 'fulfilled') {
        allQueues = [...allQueues, ...result.value];
      } else {
        errors.push({
          divisionId: divisions[index].id,
          error: result.reason.message
        });
      }
    });

  } catch (error) {
    console.error('Fatal error during queue aggregation:', error);
    throw error;
  }

  return {
    queues: allQueues,
    errors: errors,
    totalQueues: allQueues.length
  };
}

module.exports = { fetchAllQueues };

Concurrency Strategy: Using Promise.allSettled allows the script to fetch queues from multiple divisions concurrently. This significantly reduces execution time compared to sequential fetching. However, be mindful of Genesys Cloud rate limits. If your organization has many divisions, consider adding a concurrency limiter (e.g., using p-limit) to avoid triggering 429 errors.

Complete Working Example

Below is the complete, copy-pasteable Node.js script. Save this as fetch-all-queues.js and run it with node fetch-all-queues.js.

#!/usr/bin/env node

const { PureCloudPlatformClientV2 } = require('@genesyscloud/genesyscloud-platform-client-v2');

// Configuration
const CLIENT_ID = process.env.GENESYS_CLIENT_ID || 'YOUR_CLIENT_ID';
const CLIENT_SECRET = process.env.GENESYS_CLIENT_SECRET || 'YOUR_CLIENT_SECRET';
const ENVIRONMENT = process.env.GENESYS_ENVIRONMENT || 'mypurecloud.com';

// Initialize Platform Client
const platformClient = new PureCloudPlatformClientV2();

async function main() {
  try {
    // 1. Authenticate
    console.log('Authenticating...');
    await platformClient.authApi.clientCredentialsLogin(CLIENT_ID, CLIENT_SECRET);
    
    // Verify token
    const token = await platformClient.authApi.getAccessToken();
    if (!token) {
      throw new Error('Failed to obtain access token.');
    }
    console.log('Authenticated successfully.');

    // 2. Fetch All Divisions
    const divisionsApi = platformClient.DivisionsApi;
    let allDivisions = [];
    let divPageNumber = 1;
    let divHasNext = true;

    while (divHasNext) {
      const divResponse = await divisionsApi.getDivisions({
        expand: 'divisions',
        pageNumber: divPageNumber,
        pageSize: 250
      });

      if (divResponse.entities && divResponse.entities.length > 0) {
        allDivisions = [...allDivisions, ...divResponse.entities];
      }

      divHasNext = !!divResponse.nextPageUri;
      divPageNumber++;
    }

    console.log(`Discovered ${allDivisions.length} divisions.`);

    // 3. Fetch Queues for Each Division
    const queuesApi = platformClient.QueuesApi;
    let allQueues = [];
    let failedDivisions = [];

    // Concurrency limit to avoid rate limiting
    const CONCURRENCY_LIMIT = 5;
    const queueFetchTasks = [];

    for (const division of allDivisions) {
      queueFetchTasks.push(async () => {
        try {
          let queuesForDiv = [];
          let nextPageUri = null;
          let loopCount = 0;
          const maxLoops = 50; // Safety limit

          do {
            let response;
            if (nextPageUri) {
              response = await queuesApi.getQueuesWithHttpInfo({
                divisionId: division.id,
                nextPageUri: nextPageUri
              });
            } else {
              response = await queuesApi.getQueues({
                divisionId: division.id,
                pageSize: 250
              });
            }

            if (response.body.entities && response.body.entities.length > 0) {
              queuesForDiv = [...queuesForDiv, ...response.body.entities];
            }

            nextPageUri = response.body.nextPageUri;
            loopCount++;

            // Polite delay
            await new Promise(resolve => setTimeout(resolve, 50));

          } while (nextPageUri && loopCount < maxLoops);

          return queuesForDiv;

        } catch (error) {
          console.error(`Error fetching queues for division ${division.id}:`, error.message);
          failedDivisions.push({ id: division.id, error: error.message });
          return [];
        }
      });
    }

    // Execute tasks with concurrency limit
    // Simple implementation using a queue
    const results = [];
    const running = new Set();

    async function runTask(task) {
      const result = await task();
      results.push(result);
      running.delete(task);
      // Start next task if available
      if (queueFetchTasks.length > 0) {
        const nextTask = queueFetchTasks.shift();
        if (nextTask) runTask(nextTask);
      }
    }

    // Start initial batch
    for (let i = 0; i < Math.min(CONCURRENCY_LIMIT, queueFetchTasks.length); i++) {
      const task = queueFetchTasks.shift();
      if (task) {
        running.add(task);
        runTask(task);
      }
    }

    // Wait for all tasks to complete
    await Promise.all(Array.from(running));

    // Flatten results
    allQueues = results.flat();

    console.log(`\n--- Summary ---`);
    console.log(`Total Queues Fetched: ${allQueues.length}`);
    console.log(`Failed Divisions: ${failedDivisions.length}`);
    
    if (failedDivisions.length > 0) {
      console.log('Failed Division IDs:', failedDivisions.map(d => d.id));
    }

    // Example: Print first 5 queues
    console.log('\n--- Sample Queues ---');
    allQueues.slice(0, 5).forEach(q => {
      console.log(`ID: ${q.id}, Name: ${q.name}, Division: ${q.division.id}`);
    });

  } catch (error) {
    console.error('Fatal Error:', error);
    process.exit(1);
  }
}

main();

Common Errors & Debugging

Error: 401 Unauthorized

  • What causes it: Invalid Client ID or Client Secret, or the token has expired and the SDK failed to refresh it (rare).
  • How to fix it: Verify your credentials in the Genesys Cloud Admin UI under Apps & Integrations > OAuth Clients. Ensure the client is active.
  • Code showing the fix: The SDK handles token refresh automatically. If you see 401, re-initialize the clientCredentialsLogin call.

Error: 403 Forbidden

  • What causes it: The OAuth client does not have the queue:read scope assigned, or the client does not have access to the specific division.
  • How to fix it: Go to the OAuth Client settings and add queue:read to the scopes. If you need access to specific divisions, ensure the client is assigned to those divisions or has global read access.
  • Code showing the fix: Update the OAuth client in the Admin UI. No code change is required, but you must re-authenticate.

Error: 429 Too Many Requests

  • What causes it: You are hitting the Genesys Cloud API rate limits. This is common when fetching data from many divisions concurrently.
  • How to fix it: Implement exponential backoff and reduce concurrency. The complete example above includes a small delay and a concurrency limit.
  • Code showing the fix:
    // Add exponential backoff
    let retryCount = 0;
    const maxRetries = 3;
    
    while (retryCount < maxRetries) {
      try {
        await apiCall();
        break; // Success
      } catch (error) {
        if (error.code === 429) {
          const delay = Math.pow(2, retryCount) * 1000;
          console.log(`Rate limited. Retrying in ${delay}ms...`);
          await new Promise(resolve => setTimeout(resolve, delay));
          retryCount++;
        } else {
          throw error;
        }
      }
    }
    

Error: 404 Not Found

  • What causes it: The division ID provided does not exist, or the client does not have permission to view it.
  • How to fix it: Ensure the division ID is valid. If you are iterating through divisions returned by the DivisionsApi, this error should not occur unless permissions change mid-execution.
  • Code showing the fix: Handle 404 errors gracefully by logging and skipping the division, as shown in the fetchQueuesForDivision function.

Official References