Fetch All Queues Across Multiple Divisions Using the Genesys Cloud JavaScript SDK

Fetch All Queues Across Multiple Divisions Using the Genesys Cloud JavaScript SDK

What You Will Build

  • A Node.js script that authenticates with Genesys Cloud, retrieves all available divisions, and fetches every queue belonging to those divisions.
  • This tutorial uses the Genesys Cloud Platform SDK for JavaScript (@genesys/cloud v4+).
  • The implementation runs in a Node.js environment, handling pagination and division iteration automatically.

Prerequisites

  • OAuth Client Type: Application (Client Credentials Grant).
  • Required Scopes: queue:view is mandatory for reading queue data. If you need to filter by specific attributes, ensure your client has sufficient permissions.
  • SDK Version: @genesys/cloud v4.0.0 or later.
  • Runtime: Node.js v16 or higher.
  • Dependencies:
    • @genesys/cloud
    • dotenv (for managing environment variables securely)

Install the dependencies in your project directory:

npm install @genesys/cloud dotenv
npm install --save-dev @types/node

Create a .env file in your project root with your credentials:

GENESYS_CLOUD_REGION=us-east-1
GENESYS_CLOUD_CLIENT_ID=your_client_id
GENESYS_CLOUD_CLIENT_SECRET=your_client_secret

Authentication Setup

The Genesys Cloud JavaScript SDK handles OAuth 2.0 Client Credentials flow internally. You initialize the PlatformClient with your region, client ID, and client secret. The SDK manages token acquisition and refresh automatically.

Create a file named index.js and add the following initialization code:

require('dotenv').config();
const { PlatformClient } = require('@genesys/cloud');

// Initialize the platform client
const platformClient = new PlatformClient();

// Configure authentication
platformClient.auth.setAuthMethod(
  'ClientCredentials',
  process.env.GENESYS_CLOUD_CLIENT_ID,
  process.env.GENESYS_CLOUD_CLIENT_SECRET
);

// Set the region explicitly if not defaulting correctly
platformClient.auth.setRegion(process.env.GENESYS_CLOUD_REGION || 'us-east-1');

async function initializeClient() {
  try {
    // Trigger authentication to ensure a valid token is present
    await platformClient.auth.login();
    console.log('Authentication successful.');
    return platformClient;
  } catch (error) {
    console.error('Authentication failed:', error.message);
    process.exit(1);
  }
}

module.exports = { initializeClient, platformClient };

Implementation

Step 1: Retrieve All Divisions

Queues in Genesys Cloud are scoped to divisions. To fetch all queues, you must first identify all divisions available to your OAuth client. The SDK provides a helper method to iterate through paginated results, but for divisions, it is often cleaner to fetch the full list into an array since the count is typically manageable.

We use the OrganizationsApi to list divisions.

const { initializeClient, platformClient } = require('./index');

async function getAllDivisions() {
  const orgApi = platformClient.organizationsApi;
  const divisions = [];
  let nextUri = null;
  let page = 1;

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

  do {
    try {
      // Fetch division entities
      // The SDK method getOrganizationsOrganizationsDivisions handles pagination via nextUri
      const response = await orgApi.getOrganizationsOrganizationsDivisions({
        pageSize: 250,
        page: page,
        expand: false // We only need id and name
      });

      if (response.body && response.body.entities) {
        divisions.push(...response.body.entities);
      }

      // Check for next page
      nextUri = response.body?.nextPageUri || null;
      page++;

      // Safety break to prevent infinite loops if API behaves unexpectedly
      if (!nextUri && (response.body?.entities?.length || 0) < 250) {
        nextUri = null;
      }

    } catch (error) {
      console.error(`Error fetching division page ${page}:`, error.message);
      throw error;
    }
  } while (nextUri);

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

Expected Response Structure:
The response body contains an entities array. Each entity has an id (UUID) and name.

{
  "entities": [
    {
      "id": "3c8d9a12-4b5e-6f7a-8b9c-0d1e2f3a4b5c",
      "name": "Default",
      "description": "The default division",
      "externalId": null,
      "self": {
        "href": "/api/v2/organizations/divisions/3c8d9a12-4b5e-6f7a-8b9c-0d1e2f3a4b5c"
      }
    }
  ],
  "pageSize": 250,
  "pageNumber": 1,
  "total": 1
}

Step 2: Fetch Queues Per Division

Now that you have the list of divisions, you must iterate through each division ID and call the QueuesApi to retrieve queues. The getQueuesQueues endpoint allows filtering by divisionId.

Critical Note on Rate Limiting:
If you have many divisions, calling the API once per division can trigger 429 Too Many Requests errors. Genesys Cloud APIs generally allow ~10-20 requests per second per client. You must implement a delay or concurrency control.

We will use a simple sequential approach with a small delay for stability in this tutorial. For high-volume environments, consider using a promise pool or async queue library.

const { platformClient } = require('./index');

async function getQueuesForDivision(divisionId, divisionName) {
  const queuesApi = platformClient.queuesApi;
  const allQueues = [];
  let nextUri = null;
  let page = 1;
  const pageSize = 100; // Max allowed is usually 200, but 100 is safer for memory

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

  do {
    try {
      const response = await queuesApi.getQueuesQueues({
        divisionId: divisionId,
        pageSize: pageSize,
        page: page,
        expand: null // Do not expand unless necessary to save bandwidth
      });

      if (response.body && response.body.entities) {
        allQueues.push(...response.body.entities);
      }

      nextUri = response.body?.nextPageUri || null;
      page++;

      // Break if no more pages
      if (!nextUri && (response.body?.entities?.length || 0) < pageSize) {
        nextUri = null;
      }

    } catch (error) {
      console.error(`    Error fetching queues for division ${divisionId}:`, error.message);
      
      // Handle specific HTTP errors
      if (error.code === 429) {
        console.log('    Rate limited. Waiting 5 seconds before retry...');
        await new Promise(resolve => setTimeout(resolve, 5000));
        continue; // Retry the same page
      }
      throw error;
    }
  } while (nextUri);

  console.log(`    Found ${allQueues.length} queues.`);
  return allQueues;
}

OAuth Scope Requirement:
The call getQueuesQueues requires the queue:view scope. If your OAuth client lacks this scope, the API will return a 403 Forbidden error.

Step 3: Aggregate and Process Results

Combine the division listing and queue fetching logic into a single orchestrator function. This function will iterate through divisions, fetch queues, and aggregate them into a single data structure.

async function fetchAllQueues() {
  const { initializeClient } = require('./index');
  await initializeClient();

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

    if (divisions.length === 0) {
      console.log('No divisions found. Exiting.');
      return [];
    }

    const allQueuesMap = new Map(); // Map<DivisionName, Queue[]>

    // Step 2: Iterate Divisions and Fetch Queues
    for (const division of divisions) {
      try {
        const queues = await getQueuesForDivision(division.id, division.name);
        allQueuesMap.set(division.name, queues);

        // Introduce a small delay to be polite to the API and avoid rate limits
        // 200ms delay between division requests
        await new Promise(resolve => setTimeout(resolve, 200));
      } catch (divisionError) {
        console.error(`Failed to process division: ${division.name}`, divisionError);
        // Continue to next division instead of failing entirely
        allQueuesMap.set(division.name, []);
      }
    }

    // Step 3: Output Results
    console.log('\n--- Summary ---');
    console.log(`Total Divisions Processed: ${divisions.length}`);
    
    let totalQueueCount = 0;
    for (const [divisionName, queues] of allQueuesMap.entries()) {
      totalQueueCount += queues.length;
      console.log(`Division: ${divisionName} | Queue Count: ${queues.length}`);
    }
    console.log(`Total Queues Found: ${totalQueueCount}`);

    return allQueuesMap;

  } catch (error) {
    console.error('Fatal error in fetchAllQueues:', error);
    throw error;
  }
}

// Run the script
fetchAllQueues().catch(console.error);

Complete Working Example

Below is the full, copy-pasteable script. Save this as fetch-all-queues.js. Ensure your .env file is present and populated.

require('dotenv').config();
const { PlatformClient } = require('@genesys/cloud');

// --- Configuration ---
const REGION = process.env.GENESYS_CLOUD_REGION || 'us-east-1';
const CLIENT_ID = process.env.GENESYS_CLOUD_CLIENT_ID;
const CLIENT_SECRET = process.env.GENESYS_CLOUD_CLIENT_SECRET;

if (!CLIENT_ID || !CLIENT_SECRET) {
  console.error('Missing GENESYS_CLOUD_CLIENT_ID or GENESYS_CLOUD_CLIENT_SECRET in .env file');
  process.exit(1);
}

// --- Initialization ---
const platformClient = new PlatformClient();
platformClient.auth.setAuthMethod('ClientCredentials', CLIENT_ID, CLIENT_SECRET);
platformClient.auth.setRegion(REGION);

async function authenticate() {
  try {
    await platformClient.auth.login();
    console.log('Authenticated successfully.');
  } catch (error) {
    console.error('Authentication failed:', error.message);
    process.exit(1);
  }
}

// --- Core Functions ---

/**
 * Fetches all divisions accessible to the OAuth client.
 * @returns {Array} List of division objects { id, name }
 */
async function getAllDivisions() {
  const orgApi = platformClient.organizationsApi;
  const divisions = [];
  let nextPageUri = null;
  let page = 1;
  const pageSize = 250;

  console.log('Step 1: Fetching divisions...');

  do {
    try {
      const response = await orgApi.getOrganizationsOrganizationsDivisions({
        pageSize: pageSize,
        page: page,
        expand: false
      });

      if (response.body && response.body.entities) {
        divisions.push(...response.body.entities);
      }

      nextPageUri = response.body?.nextPageUri;
      page++;

      // Break condition: no next page URI and fewer entities than page size
      if (!nextPageUri && (response.body?.entities?.length || 0) < pageSize) {
        nextPageUri = null;
      }

    } catch (error) {
      console.error(`Error fetching division page ${page}:`, error.message);
      throw error;
    }
  } while (nextPageUri);

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

/**
 * Fetches all queues for a specific division.
 * @param {string} divisionId - The UUID of the division.
 * @param {string} divisionName - The name of the division (for logging).
 * @returns {Array} List of queue objects.
 */
async function getQueuesForDivision(divisionId, divisionName) {
  const queuesApi = platformClient.queuesApi;
  const queues = [];
  let nextPageUri = null;
  let page = 1;
  const pageSize = 100;

  console.log(`  Step 2: Fetching queues for division "${divisionName}"...`);

  do {
    try {
      const response = await queuesApi.getQueuesQueues({
        divisionId: divisionId,
        pageSize: pageSize,
        page: page,
        expand: null // Avoid expanding members/stats to keep payload small
      });

      if (response.body && response.body.entities) {
        queues.push(...response.body.entities);
      }

      nextPageUri = response.body?.nextPageUri;
      page++;

      if (!nextPageUri && (response.body?.entities?.length || 0) < pageSize) {
        nextPageUri = null;
      }

    } catch (error) {
      // Handle Rate Limiting
      if (error.code === 429) {
        console.log('    Rate limit (429) hit. Retrying in 5 seconds...');
        await new Promise(resolve => setTimeout(resolve, 5000));
        continue; // Retry the same page
      }
      // Handle Forbidden (403) - likely scope issue
      if (error.code === 403) {
        console.error(`    Forbidden (403) accessing queues for division ${divisionName}. Check OAuth scopes.`);
        throw error;
      }
      console.error(`    Error fetching queues page ${page} for division ${divisionName}:`, error.message);
      throw error;
    }
  } while (nextPageUri);

  console.log(`    Retrieved ${queues.length} queues.`);
  return queues;
}

// --- Main Execution ---

async function main() {
  await authenticate();

  try {
    const divisions = await getAllDivisions();

    if (divisions.length === 0) {
      console.log('No divisions found. Exiting.');
      process.exit(0);
    }

    const results = {};
    let totalQueues = 0;

    for (const division of divisions) {
      try {
        const queues = await getQueuesForDivision(division.id, division.name);
        results[division.name] = queues;
        totalQueues += queues.length;

        // Polite delay to avoid hitting rate limits across rapid sequential calls
        await new Promise(resolve => setTimeout(resolve, 200));
      } catch (error) {
        console.error(`Failed to fetch queues for division ${division.name}:`, error.message);
        results[division.name] = [];
      }
    }

    console.log('\n--- Final Summary ---');
    console.log(`Total Divisions: ${divisions.length}`);
    console.log(`Total Queues: ${totalQueues}`);
    
    // Example: Print first queue name from each division
    for (const [divName, queues] of Object.entries(results)) {
      if (queues.length > 0) {
        console.log(`  ${divName}: First Queue -> ${queues[0].name}`);
      }
    }

  } catch (error) {
    console.error('Unhandled error in main:', error);
    process.exit(1);
  }
}

main();

Common Errors & Debugging

Error: 401 Unauthorized

  • Cause: The OAuth token is expired, invalid, or the client credentials are incorrect.
  • Fix: Verify that GENESYS_CLOUD_CLIENT_ID and GENESYS_CLOUD_CLIENT_SECRET in your .env file match an active OAuth Client in Genesys Cloud. Ensure the client is not disabled.
  • Code Check: The SDK usually refreshes tokens automatically. If you see this error immediately on startup, check your credentials. If it happens mid-execution, check your network connectivity.

Error: 403 Forbidden

  • Cause: The OAuth Client does not have the required scope queue:view.
  • Fix: Go to the Genesys Cloud Admin Console > Platform > OAuth Clients. Select your client, go to the Scopes tab, and ensure queue:view is checked. Save the changes.
  • Code Check: You can verify the scopes attached to your token by logging platformClient.auth.getAccessToken() and decoding the JWT payload (specifically the scope claim) using a tool like jwt.io.

Error: 429 Too Many Requests

  • Cause: You are sending requests faster than the API allows. The Genesys Cloud API has rate limits per client and per tenant.
  • Fix: Implement exponential backoff or fixed delays. The provided code includes a 200ms delay between divisions and a 5-second retry on 429 errors.
  • Debugging: Check the Retry-After header in the response if available. The SDK may not expose this header directly in all error objects, so logging error.response.headers can be helpful.

Error: Division Not Found / Empty Results

  • Cause: The OAuth Client is scoped to a specific division or set of divisions, or you are using a User Context instead of Application Context.
  • Fix: Ensure you are using ClientCredentials (Application) flow, not AuthorizationCode (User) flow, unless you intentionally want user-scoped results. Application clients typically see all divisions they have access to. If the client is restricted to specific divisions in the admin console, you will only see those divisions.

Official References