Triggering real-time Agent Assist suggestions based on conversation keywords using the Genesys Cloud Agent Assist API and Node.js
What You Will Build
- A Node.js TypeScript module that submits conversation transcripts to the Genesys Cloud Agent Assist API to retrieve real-time knowledge article suggestions triggered by keyword matching.
- This implementation uses the
POST /api/v2/knowledge/agentassist/suggestionsendpoint and the official Genesys Cloud Node SDK. - The code is written in TypeScript for Node.js 18+ with production-grade error handling, token caching, and exponential backoff for rate limiting.
Prerequisites
- OAuth 2.0 Confidential Client (Client Credentials Flow) registered in Genesys Cloud
- Required scopes:
agentassist:manage,agentassist:view,knowledge:articles:view - Genesys Cloud Node SDK v4+ (
@genesyscloud/genesys-cloud-node) - Node.js 18+ runtime
- Dependencies:
axios,dotenv,typescript,@types/node
Authentication Setup
Genesys Cloud APIs require a valid Bearer token obtained via the OAuth 2.0 client credentials flow. The token expires after one hour, so your application must cache the token and request a new one when expiration occurs.
import axios from 'axios';
const OAUTH_URL = process.env.GENESYS_OAUTH_URL || 'https://api.mypurecloud.com';
const CLIENT_ID = process.env.GENESYS_CLIENT_ID!;
const CLIENT_SECRET = process.env.GENESYS_CLIENT_SECRET!;
const SCOPES = 'agentassist:manage agentassist:view knowledge:articles:view';
interface TokenResponse {
access_token: string;
expires_in: number;
token_type: string;
}
let cachedToken: string | null = null;
let tokenExpiry: number = 0;
async function getAccessToken(): Promise<string> {
const now = Date.now();
if (cachedToken && now < tokenExpiry) {
return cachedToken;
}
try {
const response = await axios.post<TokenResponse>(
`${OAUTH_URL}/login/oauth2/token`,
{
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
grant_type: 'client_credentials',
scope: SCOPES,
},
{
headers: { 'Content-Type': 'application/json' },
}
);
cachedToken = response.data.access_token;
tokenExpiry = now + (response.data.expires_in * 1000) - 5000; // Refresh 5 seconds early
return cachedToken;
} catch (error) {
if (axios.isAxiosError(error)) {
console.error('OAuth token request failed:', error.response?.status, error.response?.data);
}
throw new Error('Authentication failed. Verify client credentials and scopes.');
}
}
The token request uses client_credentials grant type. The response contains the access_token and expires_in duration in seconds. The caching logic subtracts five seconds from the expiration window to prevent edge-case token expiration during concurrent API calls. If the OAuth endpoint returns a 401 or 400 status, the function throws a descriptive error to halt execution before making downstream requests.
Implementation
Step 1: Initialize the Platform Client and Configure Retry Logic
The Genesys Cloud Node SDK abstracts HTTP details but still requires explicit token injection. You must initialize the PlatformClient and attach the token provider to the AgentassistApi instance. Production systems must handle 429 Too Many Requests responses, which occur when you exceed the per-client rate limit for the Agent Assist service.
import { PlatformClient } from '@genesyscloud/genesys-cloud-node';
const platformClient = new PlatformClient();
async function getAgentAssistApi() {
const token = await getAccessToken();
platformClient.setAccessToken(token);
return platformClient.AgentassistApi();
}
async function retryOnRateLimit<T>(
fn: () => Promise<T>,
maxRetries = 3,
baseDelayMs = 1000
): Promise<T> {
let attempt = 0;
while (true) {
try {
return await fn();
} catch (error: any) {
if (error.status === 429 && attempt < maxRetries) {
const retryAfter = error.headers?.['retry-after']
? parseInt(error.headers['retry-after'], 10) * 1000
: baseDelayMs * Math.pow(2, attempt);
console.log(`Rate limited. Retrying in ${retryAfter}ms (attempt ${attempt + 1}/${maxRetries})`);
await new Promise(resolve => setTimeout(resolve, retryAfter));
attempt++;
} else {
throw error;
}
}
}
}
The retryOnRateLimit wrapper implements exponential backoff. Genesys Cloud returns a Retry-After header in 429 responses. The code parses this header when available, otherwise it calculates a backoff delay using a base multiplier. The retry loop executes up to three times before propagating the error. This prevents cascading failures during high-volume conversation streaming.
Step 2: Submit Conversation Transcript for Keyword Matching
The core operation uses the POST /api/v2/knowledge/agentassist/suggestions endpoint. You must structure the transcript as an ordered array of utterances. Genesys Cloud analyzes the text field against configured keyword rules and semantic models. The speaker field determines which side of the conversation triggers the match.
Full HTTP Request/Response Cycle:
POST /api/v2/knowledge/agentassist/suggestions HTTP/1.1
Host: api.mypurecloud.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
{
"conversationId": "conv-98765-abc-def",
"userId": "agent-user-id-12345",
"agentAssistConfigurationId": "agent-assist-config-id-67890",
"transcript": [
{
"text": "Customer: My internet keeps dropping every evening after 6 PM.",
"speaker": "customer",
"timestamp": "2024-06-10T16:05:00.000Z"
},
{
"text": "Agent: I understand. Have you tried power cycling the modem?",
"speaker": "agent",
"timestamp": "2024-06-10T16:05:12.000Z"
},
{
"text": "Customer: Yes, I did that twice. The lights on the router blink red.",
"speaker": "customer",
"timestamp": "2024-06-10T16:05:25.000Z"
}
]
}
HTTP/1.1 200 OK
Content-Type: application/json
{
"suggestions": [
{
"articleId": "kb-article-router-red-light-troubleshooting",
"relevanceScore": 0.94,
"title": "Router Red Light Diagnostics and Resolution",
"url": "https://kb.example.com/articles/router-red-light",
"category": "Technical Support",
"matchedKeywords": ["router blink red", "internet dropping"]
},
{
"articleId": "kb-article-evening-bandwidth-throttling",
"relevanceScore": 0.87,
"title": "Network Congestion During Peak Hours",
"url": "https://kb.example.com/articles/peak-hour-congestion",
"category": "Network Performance",
"matchedKeywords": ["dropping every evening", "6 PM"]
}
]
}
The SDK call mirrors this HTTP request. You must provide a unique conversationId, the authenticated userId, and optionally target a specific agentAssistConfigurationId. If you omit the configuration ID, Genesys Cloud evaluates the transcript against all active Agent Assist configurations assigned to the user.
import { AgentassistApi, PostKnowledgeAgentassistSuggestionsRequest } from '@genesyscloud/genesys-cloud-node';
async function triggerAgentAssistSuggestions(
conversationId: string,
userId: string,
transcriptItems: Array<{ text: string; speaker: 'customer' | 'agent' | 'system'; timestamp: string }>,
configurationId?: string
): Promise<any> {
const api = await getAgentAssistApi();
const requestPayload: PostKnowledgeAgentassistSuggestionsRequest = {
conversationId,
userId,
transcript: transcriptItems,
};
if (configurationId) {
requestPayload.agentAssistConfigurationId = configurationId;
}
return await retryOnRateLimit(() => api.postKnowledgeAgentassistSuggestions(requestPayload));
}
The postKnowledgeAgentassistSuggestions method serializes the payload and handles the underlying HTTP POST. The transcript array must be chronologically ordered. Genesys Cloud processes the array sequentially to maintain context window alignment. The speaker field accepts customer, agent, or system. Keyword rules in the Genesys Cloud console can be configured to trigger only on customer utterances, which reduces false positives from agent paraphrasing.
Step 3: Process Suggestions and Handle Edge Cases
The response contains a suggestions array. Each suggestion includes a relevanceScore between 0.0 and 1.0. Production applications should filter suggestions below a confidence threshold and deduplicate results based on articleId. You must also handle scenarios where the transcript yields no matches.
interface AgentAssistResult {
articleId: string;
title: string;
url: string;
relevanceScore: number;
matchedKeywords: string[];
}
async function processSuggestions(
conversationId: string,
userId: string,
transcript: Array<{ text: string; speaker: 'customer' | 'agent' | 'system'; timestamp: string }>,
configId?: string,
minRelevance: number = 0.75
): Promise<AgentAssistResult[]> {
try {
const response = await triggerAgentAssistSuggestions(conversationId, userId, transcript, configId);
if (!response.body?.suggestions || response.body.suggestions.length === 0) {
console.log(`No suggestions returned for conversation ${conversationId}`);
return [];
}
const filteredSuggestions: AgentAssistResult[] = [];
const seenIds = new Set<string>();
for (const suggestion of response.body.suggestions) {
if (suggestion.relevanceScore >= minRelevance && !seenIds.has(suggestion.articleId)) {
filteredSuggestions.push({
articleId: suggestion.articleId,
title: suggestion.title,
url: suggestion.url,
relevanceScore: suggestion.relevanceScore,
matchedKeywords: suggestion.matchedKeywords || []
});
seenIds.add(suggestion.articleId);
}
}
return filteredSuggestions.sort((a, b) => b.relevanceScore - a.relevanceScore);
} catch (error) {
console.error('Failed to retrieve Agent Assist suggestions:', error);
throw error;
}
}
The processing function applies a minRelevance threshold to suppress low-confidence matches. The seenIds set prevents duplicate articles from appearing when multiple keyword rules trigger the same knowledge base entry. The final array is sorted by descending relevanceScore to prioritize the most accurate matches for UI rendering. If the API returns a 400 status, it typically indicates malformed transcript timestamps or invalid speaker values. The error handler logs the failure and propagates it for upstream retry or alerting.
Complete Working Example
import dotenv from 'dotenv';
dotenv.config();
import axios from 'axios';
import { PlatformClient } from '@genesyscloud/genesys-cloud-node';
// Configuration
const OAUTH_URL = process.env.GENESYS_OAUTH_URL || 'https://api.mypurecloud.com';
const CLIENT_ID = process.env.GENESYS_CLIENT_ID!;
const CLIENT_SECRET = process.env.GENESYS_CLIENT_SECRET!;
const SCOPES = 'agentassist:manage agentassist:view knowledge:articles:view';
// Token Cache
let cachedToken: string | null = null;
let tokenExpiry: number = 0;
async function getAccessToken(): Promise<string> {
const now = Date.now();
if (cachedToken && now < tokenExpiry) {
return cachedToken;
}
try {
const response = await axios.post(
`${OAUTH_URL}/login/oauth2/token`,
{
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
grant_type: 'client_credentials',
scope: SCOPES,
},
{ headers: { 'Content-Type': 'application/json' } }
);
cachedToken = response.data.access_token;
tokenExpiry = now + (response.data.expires_in * 1000) - 5000;
return cachedToken;
} catch (error) {
if (axios.isAxiosError(error)) {
console.error('OAuth failed:', error.response?.status, error.response?.data);
}
throw new Error('Authentication failed.');
}
}
const platformClient = new PlatformClient();
async function getAgentAssistApi() {
const token = await getAccessToken();
platformClient.setAccessToken(token);
return platformClient.AgentassistApi();
}
async function retryOnRateLimit<T>(fn: () => Promise<T>, maxRetries = 3, baseDelayMs = 1000): Promise<T> {
let attempt = 0;
while (true) {
try {
return await fn();
} catch (error: any) {
if (error.status === 429 && attempt < maxRetries) {
const retryAfter = error.headers?.['retry-after']
? parseInt(error.headers['retry-after'], 10) * 1000
: baseDelayMs * Math.pow(2, attempt);
console.log(`Rate limited. Retrying in ${retryAfter}ms`);
await new Promise(resolve => setTimeout(resolve, retryAfter));
attempt++;
} else {
throw error;
}
}
}
}
interface AgentAssistResult {
articleId: string;
title: string;
url: string;
relevanceScore: number;
matchedKeywords: string[];
}
async function processSuggestions(
conversationId: string,
userId: string,
transcript: Array<{ text: string; speaker: 'customer' | 'agent' | 'system'; timestamp: string }>,
configId?: string,
minRelevance: number = 0.75
): Promise<AgentAssistResult[]> {
try {
const api = await getAgentAssistApi();
const payload = {
conversationId,
userId,
transcript,
agentAssistConfigurationId: configId,
};
const response = await retryOnRateLimit(() => api.postKnowledgeAgentassistSuggestions(payload));
if (!response.body?.suggestions || response.body.suggestions.length === 0) {
return [];
}
const filtered: AgentAssistResult[] = [];
const seen = new Set<string>();
for (const s of response.body.suggestions) {
if (s.relevanceScore >= minRelevance && !seen.has(s.articleId)) {
filtered.push({
articleId: s.articleId,
title: s.title,
url: s.url,
relevanceScore: s.relevanceScore,
matchedKeywords: s.matchedKeywords || []
});
seen.add(s.articleId);
}
}
return filtered.sort((a, b) => b.relevanceScore - a.relevanceScore);
} catch (error) {
console.error('Agent Assist processing failed:', error);
throw error;
}
}
// Execution
async function main() {
const sampleTranscript = [
{ text: "Customer: My router keeps flashing a red light and I cannot connect.", speaker: "customer" as const, timestamp: "2024-06-10T16:05:00.000Z" },
{ text: "Agent: Let me check that for you. Are you using the default gateway?", speaker: "agent" as const, timestamp: "2024-06-10T16:05:10.000Z" },
{ text: "Customer: Yes, I reset it but the red light persists after 10 minutes.", speaker: "customer" as const, timestamp: "2024-06-10T16:05:25.000Z" }
];
const results = await processSuggestions(
"conv-demo-12345",
"agent-id-98765",
sampleTranscript,
process.env.AGENT_ASSIST_CONFIG_ID,
0.7
);
console.log("Retrieved suggestions:", JSON.stringify(results, null, 2));
}
main().catch(console.error);
Common Errors & Debugging
Error: 401 Unauthorized
- Cause: The access token expired, was revoked, or lacks the required scopes. The OAuth client credentials may be incorrect.
- Fix: Verify the
GENESYS_CLIENT_IDandGENESYS_CLIENT_SECRETenvironment variables. Ensure the OAuth client in Genesys Cloud hasagentassist:manageandknowledge:articles:viewscopes enabled. Implement token refresh before expiration. - Code Fix: The
getAccessTokenfunction already implements time-based cache invalidation. Add explicit scope validation in your OAuth client configuration.
Error: 403 Forbidden
- Cause: The authenticated client lacks permissions to access the Agent Assist configuration, or the target
agentAssistConfigurationIdis disabled or not assigned to the user. - Fix: Navigate to the Genesys Cloud Admin console and verify the Agent Assist configuration status. Ensure the OAuth client has organizational permissions for Knowledge and Agent Assist.
- Code Fix: Log the
configurationIdbeing used. Test with a known active configuration ID before deploying to production.
Error: 429 Too Many Requests
- Cause: The application exceeded the per-client rate limit for the Agent Assist service. Real-time streaming often triggers this when processing high-velocity transcripts.
- Fix: Implement exponential backoff and respect the
Retry-Afterheader. Batch transcript submissions if your use case allows slight latency. - Code Fix: The
retryOnRateLimitwrapper handles this automatically. IncreasemaxRetriesto 5 if your architecture tolerates higher latency.
Error: 400 Bad Request
- Cause: Malformed transcript payload. Common issues include invalid ISO 8601 timestamps, missing
speakerenum values, or exceeding the maximum transcript length limit. - Fix: Validate all
timestampfields against ISO 8601 format. Ensurespeakerstrictly matchescustomer,agent, orsystem. Trim transcripts to the last 10-15 utterances to stay within payload limits. - Code Fix: Add a pre-submission validation step that checks
Date.parse(item.timestamp) !== NaNbefore calling the API.