Increase Genesys Cloud Data Action Timeout Limits for Long-Running API Calls
What You Will Build
- A Node.js integration that executes a Genesys Cloud Data Action with a configurable timeout exceeding the default 3-second limit.
- The code uses the Genesys Cloud PureCloud Platform Client SDK for JavaScript and the underlying REST API for advanced timeout configuration.
- The programming language covered is JavaScript (Node.js) with TypeScript type definitions.
Prerequisites
- OAuth Client Type: Service Account or OAuth 2.0 Client Credentials flow.
- Required Scopes:
analytics:query:read(if querying data)integrations:action:execute(for generic data actions)customobjects:instance:reador specific object permissions if interacting with Custom Objects via Data Actions.organization:read(often required for environment context).
- SDK Version:
@genesyscloud/purecloud-platform-client-v2version 160.0.0 or higher. - Runtime Requirements: Node.js 18 LTS or higher.
- External Dependencies:
@genesyscloud/purecloud-platform-client-v2dotenv(for environment variable management)
Authentication Setup
Genesys Cloud APIs require OAuth 2.0 Bearer tokens. The standard Client Credentials flow is used for server-to-server integrations like Data Actions. The SDK handles token caching and automatic refresh, but you must initialize the PlatformClient correctly with your environment.
import { PlatformClient } from '@genesyscloud/purecloud-platform-client-v2';
import dotenv from 'dotenv';
dotenv.config();
// Initialize the Platform Client
const pureCloud = PlatformClient;
pureCloud.setEnvironment('mypurecloud.com'); // Replace with your specific environment, e.g., 'usw2.pure.cloud'
// Authenticate using Client Credentials
// The SDK caches the token and refreshes it automatically before expiration
pureCloud.authenticate({
client_id: process.env.GENESYS_CLIENT_ID,
client_secret: process.env.GENESYS_CLIENT_SECRET,
grant_type: 'client_credentials',
scope: process.env.GENESYS_SCOPES // e.g., 'integrations:action:execute analytics:query:read'
}).then(() => {
console.log('Authentication successful.');
// Proceed with API calls
}).catch((error) => {
console.error('Authentication failed:', error);
process.exit(1);
});
Implementation
Step 1: Understand the Data Action Timeout Constraint
The Genesys Cloud Data Action framework, particularly when invoked via the POST /api/v2/integrations/actions/{actionId}/execute endpoint, has a default server-side timeout. For synchronous execution, this is often capped at 3 to 5 seconds depending on the specific action type and current load. If your underlying operation (such as a complex SQL query, a large Custom Object export, or an external HTTP call via a webhook action) exceeds this duration, the API returns a 504 Gateway Timeout or a 408 Request Timeout.
You cannot “increase” the synchronous timeout limit via a simple header. The limit is enforced by the API gateway. To handle operations taking 5 seconds or more, you must switch from a Synchronous execution model to an Asynchronous execution model.
The asynchronous pattern works as follows:
- Submit the request to initiate the action.
- Receive a
202 Acceptedresponse with aLocationheader or ajobId. - Poll the status endpoint using the
jobIduntil the status iscompleted,failed, orcancelled.
Step 2: Configure the Asynchronous Data Action Request
To invoke a Data Action asynchronously, you must set the async parameter to true in the request body. This tells the Genesys Cloud engine to process the request in the background and return immediately.
Endpoint: POST /api/v2/integrations/actions/{actionId}/execute
Request Body Structure:
{
"async": true,
"parameters": {
"param1": "value1",
"param2": "value2"
}
}
JavaScript Implementation using SDK:
The SDK provides the executeAction method. However, for fine-grained control over async behavior and error handling, it is often clearer to use the raw API call via ApiClient or ensure the SDK wrapper correctly maps the async flag.
import { IntegrationsApi } from '@genesyscloud/purecloud-platform-client-v2';
const integrationsApi = new IntegrationsApi();
/**
* Executes a Data Action asynchronously.
* @param {string} actionId - The ID of the Data Action.
* @param {Object} parameters - The input parameters for the action.
* @returns {Promise<Object>} - The execution job object.
*/
async function executeAsyncDataAction(actionId, parameters) {
try {
// Construct the body with async set to true
const body = {
async: true,
parameters: parameters
};
// The SDK method for executing an action
// Note: Depending on the SDK version, the method might be 'executeAction' or 'executeIntegrationAction'
const result = await integrationsApi.executeAction(actionId, body);
console.log('Action initiated. Job ID:', result.id || result.jobId);
return result;
} catch (error) {
if (error.status === 429) {
console.warn('Rate limited. Retry after', error.headers['retry-after'], 'seconds.');
// Implement exponential backoff here
} else if (error.status === 401 || error.status === 403) {
console.error('Authentication or Authorization error. Check scopes.');
} else {
console.error('Failed to initiate action:', error.message);
}
throw error;
}
}
Step 3: Poll for Results and Handle Completion
Once the action is initiated, you receive a job identifier. You must poll the status endpoint to retrieve the final result. The endpoint for checking status is typically GET /api/v2/integrations/actions/{actionId}/executions/{executionId} or similar, depending on the specific action type. For generic integration actions, the execution ID is returned in the initial response.
Endpoint: GET /api/v2/integrations/actions/{actionId}/executions/{executionId}
JavaScript Implementation with Polling Logic:
/**
* Polls for the status of an asynchronous Data Action execution.
* @param {string} actionId - The ID of the Data Action.
* @param {string} executionId - The ID of the execution returned from Step 2.
* @param {number} maxRetries - Maximum number of polling attempts.
* @param {number} intervalMs - Time in milliseconds between polls.
* @returns {Promise<Object>} - The final result of the action.
*/
async function pollForActionResult(actionId, executionId, maxRetries = 60, intervalMs = 2000) {
let attempts = 0;
while (attempts < maxRetries) {
try {
// Fetch the execution status
const execution = await integrationsApi.getActionExecution(actionId, executionId);
const status = execution.status; // e.g., 'pending', 'running', 'completed', 'failed'
console.log(`Attempt ${attempts + 1}: Status is ${status}`);
if (status === 'completed') {
console.log('Action completed successfully.');
return execution.result;
} else if (status === 'failed') {
console.error('Action failed:', execution.error);
throw new Error(`Action failed: ${execution.error.message || 'Unknown error'}`);
} else if (status === 'cancelled') {
console.warn('Action was cancelled.');
throw new Error('Action was cancelled.');
}
// If still pending/running, wait before next poll
await new Promise(resolve => setTimeout(resolve, intervalMs));
attempts++;
} catch (error) {
if (error.status === 404) {
console.error('Execution not found. Ensure the executionId is correct.');
throw error;
} else if (error.status === 429) {
console.warn('Rate limited during polling. Waiting longer...');
await new Promise(resolve => setTimeout(resolve, intervalMs * 2));
continue; // Do not increment attempts on rate limit
} else {
console.error('Error polling status:', error.message);
throw error;
}
}
}
throw new Error(`Max retries (${maxRetries}) exceeded. Action did not complete in time.`);
}
Complete Working Example
This complete script demonstrates the full flow: authentication, initiating an asynchronous Data Action, and polling for the result. It includes error handling for rate limits, authentication failures, and timeout scenarios.
import { PlatformClient, IntegrationsApi } from '@genesyscloud/purecloud-platform-client-v2';
import dotenv from 'dotenv';
dotenv.config();
const pureCloud = PlatformClient;
pureCloud.setEnvironment(process.env.GENESYS_ENVIRONMENT || 'mypurecloud.com');
const integrationsApi = new IntegrationsApi();
/**
* Main execution function
*/
async function main() {
// 1. Authenticate
try {
await pureCloud.authenticate({
client_id: process.env.GENESYS_CLIENT_ID,
client_secret: process.env.GENESYS_CLIENT_SECRET,
grant_type: 'client_credentials',
scope: process.env.GENESYS_SCOPES
});
console.log('Authenticated successfully.');
} catch (error) {
console.error('Authentication failed:', error);
process.exit(1);
}
// 2. Define Action Parameters
const actionId = process.env.GENESYS_ACTION_ID; // e.g., '12345678-1234-1234-1234-123456789012'
const actionParameters = {
queryId: 'my-long-running-query-id',
limit: 10000
};
if (!actionId) {
console.error('GENESYS_ACTION_ID environment variable is not set.');
process.exit(1);
}
try {
// 3. Execute Async Action
console.log(`Initiating async action: ${actionId}`);
const executionResult = await executeAsyncDataAction(actionId, actionParameters);
// Extract execution ID from the response
// The response structure can vary slightly by action type, but typically contains an 'id' or 'executionId'
const executionId = executionResult.id || executionResult.executionId;
if (!executionId) {
throw new Error('Could not determine execution ID from response.');
}
console.log(`Execution ID: ${executionId}. Starting poll...`);
// 4. Poll for Results
const finalResult = await pollForActionResult(actionId, executionId);
console.log('Final Result:', JSON.stringify(finalResult, null, 2));
} catch (error) {
console.error('Overall execution failed:', error);
process.exit(1);
}
}
/**
* Executes a Data Action asynchronously.
*/
async function executeAsyncDataAction(actionId, parameters) {
try {
const body = {
async: true,
parameters: parameters
};
// Use the SDK's executeAction method
const result = await integrationsApi.executeAction(actionId, body);
return result;
} catch (error) {
handleApiError(error, 'Execute Action');
throw error;
}
}
/**
* Polls for the status of an asynchronous Data Action execution.
*/
async function pollForActionResult(actionId, executionId, maxRetries = 60, intervalMs = 2000) {
let attempts = 0;
while (attempts < maxRetries) {
try {
const execution = await integrationsApi.getActionExecution(actionId, executionId);
const status = execution.status;
if (status === 'completed') {
return execution.result;
} else if (status === 'failed') {
throw new Error(`Action failed: ${execution.error?.message || 'Unknown error'}`);
} else if (status === 'cancelled') {
throw new Error('Action was cancelled.');
}
// Wait before next poll
await new Promise(resolve => setTimeout(resolve, intervalMs));
attempts++;
} catch (error) {
if (error.status === 404) {
throw new Error('Execution not found.');
} else if (error.status === 429) {
// Increase wait time on rate limit
await new Promise(resolve => setTimeout(resolve, intervalMs * 2));
continue;
} else {
throw error;
}
}
}
throw new Error(`Max retries (${maxRetries}) exceeded.`);
}
/**
* Helper to handle API errors consistently
*/
function handleApiError(error, context) {
if (error.status === 429) {
console.warn(`[Rate Limited] ${context}. Retry after ${error.headers?.['retry-after'] || 'unknown'} seconds.`);
} else if (error.status >= 400 && error.status < 500) {
console.error(`[Client Error] ${context}: ${error.message}`);
} else {
console.error(`[Server Error] ${context}: ${error.message}`);
}
}
// Run the main function
main();
Common Errors & Debugging
Error: 504 Gateway Timeout / 408 Request Timeout
- What causes it: The synchronous request exceeded the server-side timeout limit (typically 3-5 seconds). This is the core problem this tutorial addresses.
- How to fix it: You must switch to the asynchronous execution model as shown in Step 2. Set
async: truein the request body. Do not attempt to increase the timeout via headers; the gateway will reject long-running synchronous requests.
Error: 429 Too Many Requests
- What causes it: You have exceeded the rate limit for the
integrations:action:executeorgetActionExecutionendpoints. Genesys Cloud enforces strict rate limits to protect backend resources. - How to fix it: Implement exponential backoff. The code example above includes a check for
429status codes and increases the wait time before retrying. Always respect theRetry-Afterheader if present in the response.
Error: 401 Unauthorized / 403 Forbidden
- What causes it: The OAuth token is expired, invalid, or lacks the required scopes.
- How to fix it: Ensure your
GENESYS_SCOPESenvironment variable includesintegrations:action:execute. If using the SDK, ensure thePlatformClientis authenticated before making calls. The SDK handles token refresh, but if the initial token is invalid, the call will fail.
Error: 400 Bad Request - “Invalid Action ID”
- What causes it: The
actionIdprovided does not exist or is not accessible to the authenticated user/service account. - How to fix it: Verify the
actionIdin the Genesys Cloud Admin console under Integrations > Data Actions. Ensure the Service Account has permission to view and execute this specific action.
Error: Action Status Stuck in ‘Running’
- What causes it: The underlying data action is taking longer than expected, or the backend service processing the action is overloaded.
- How to fix it: Increase the
maxRetriesandintervalMsin thepollForActionResultfunction. If the action consistently hangs, investigate the underlying logic of the Data Action (e.g., database query performance, external API latency).