Implementing NICE Cognigy.AI Function Calling via REST API with TypeScript
What You Will Build
A TypeScript service that extracts function intents from user utterances using the Cognigy.AI API, executes them against external services, validates outputs, caches definitions, logs metrics, and exposes a dynamic function registry.
This tutorial uses the Cognigy.AI v1 REST API (/api/v1/ai/dialog) and standard TypeScript libraries.
The programming language is TypeScript compiled for Node.js 18+.
Prerequisites
- Cognigy.AI tenant URL and a valid API key (or OAuth2 client credentials with
cognigy.ai:readandcognigy.ai:writescopes) - Node.js 18+ and TypeScript 4.7+
npm install axios zod express uuid pino- Basic familiarity with REST API design and TypeScript type systems
Authentication Setup
Cognigy.AI supports API key authentication for server-to-server integrations. The API key is passed in the x-api-key header. If your organization enforces OAuth2, replace the header with a bearer token obtained via the client credentials flow. The code below uses the API key pattern for direct integration.
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
const COGNIGY_TENANT = process.env.COYNIGY_TENANT || 'your-tenant';
const COGNIGY_API_KEY = process.env.COYNIGY_API_KEY || '';
const BASE_URL = `https://${COGNIGY_TENANT}.cognigy.ai/api/v1`;
export const cognigyClient: AxiosInstance = axios.create({
baseURL: BASE_URL,
headers: {
'Content-Type': 'application/json',
'x-api-key': COGNIGY_API_KEY,
},
timeout: 5000,
});
The AxiosInstance encapsulates the base URL and authentication header. Every request to the Cognigy.AI platform automatically includes the required credentials. If OAuth2 is required, generate a token via POST /oauth/token and attach it as Authorization: Bearer <token> instead of x-api-key.
Implementation
Step 1: Define Function Schemas and Registry Structure
Function schemas define the expected input parameters and return types for each callable function. The registry stores these schemas and provides type-safe lookup. zod enforces validation at runtime.
import { z } from 'zod';
export const WeatherSchema = {
input: z.object({
location: z.string().min(1),
unit: z.enum(['celsius', 'fahrenheit']).default('celsius'),
}),
output: z.object({
temperature: z.number(),
condition: z.string(),
timestamp: z.string().datetime(),
}),
};
export const FlightStatusSchema = {
input: z.object({
flightNumber: z.string().regex(/^([A-Z]{2}\d{3,4})$/),
date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
}),
output: z.object({
status: z.enum(['on-time', 'delayed', 'cancelled']),
gate: z.string().nullable(),
departureTime: z.string().datetime(),
}),
};
export type FunctionSchema = {
input: z.ZodObject<any>;
output: z.ZodObject<any>;
};
export interface FunctionRegistry {
[key: string]: FunctionSchema;
}
export const functionRegistry: FunctionRegistry = {
getWeather: WeatherSchema,
checkFlight: FlightStatusSchema,
};
The registry maps function names to Zod schemas. Input schemas validate extracted parameters from Cognigy.AI. Output schemas validate responses from external services before returning them to the dialog engine.
Step 2: Invoke Cognigy.AI API for Intent Extraction
The Cognigy.AI dialog endpoint processes user utterances and returns structured intents, entities, and function triggers. The request must include a sessionId, input, and language. The response contains a functions array when the AI detects a callable action.
import { v4 as uuidv4 } from 'uuid';
interface CognigyRequest {
sessionId: string;
input: string;
language: string;
context?: Record<string, any>;
}
interface CognigyResponse {
sessionId: string;
intent: { name: string; confidence: number };
functions: Array<{ name: string; parameters: Record<string, string> }>;
entities: Array<{ name: string; value: string; confidence: number }>;
}
async function extractFunctionIntent(utterance: string): Promise<CognigyResponse> {
const payload: CognigyRequest = {
sessionId: uuidv4(),
input: utterance,
language: 'en',
};
const response = await cognigyClient.post<CognigyResponse>('/ai/dialog', payload);
return response.data;
}
Expected Request Body:
{
"sessionId": "a3f1c9d2-8b4e-4f1a-9c7d-2e5f8a1b3c4d",
"input": "What is the weather in London in fahrenheit?",
"language": "en"
}
Expected Response Body:
{
"sessionId": "a3f1c9d2-8b4e-4f1a-9c7d-2e5f8a1b3c4d",
"intent": { "name": "AskWeather", "confidence": 0.94 },
"functions": [
{
"name": "getWeather",
"parameters": { "location": "London", "unit": "fahrenheit" }
}
],
"entities": [
{ "name": "Location", "value": "London", "confidence": 0.98 },
{ "name": "Unit", "value": "fahrenheit", "confidence": 0.91 }
]
}
The AI extracts the function name and populates parameters from recognized entities. The client must map these parameters to the expected schema types before execution.
Step 3: Map Arguments and Execute External Services
Parameters returned by Cognigy.AI are strings. External services often require typed values. The execution layer maps string parameters to the schema input, calls the external service, and returns the result. This example uses a mock external service function.
async function executeExternalService(
functionName: string,
parameters: Record<string, string>
): Promise<Record<string, any>> {
switch (functionName) {
case 'getWeather':
return {
temperature: 72,
condition: 'Partly Cloudy',
timestamp: new Date().toISOString(),
};
case 'checkFlight':
return {
status: 'on-time',
gate: 'B12',
departureTime: '2024-06-15T14:30:00Z',
};
default:
throw new Error(`Unknown function: ${functionName}`);
}
}
In production, replace the switch statement with actual HTTP calls to weather APIs, flight trackers, or CRM systems. The parameters object aligns with the input schema defined in Step 1.
Step 4: Validate Outputs and Handle Execution Errors
Validation ensures external service responses match expected structures. Errors are caught and transformed into structured responses that Cognigy.AI can consume for fallback dialog paths.
function validateOutput<T>(schema: z.ZodType<T>, data: unknown): T {
const result = schema.safeParse(data);
if (!result.success) {
throw new Error(`Validation failed: ${result.error.message}`);
}
return result.data;
}
export interface FunctionExecutionResult {
success: boolean;
data?: Record<string, any>;
error?: {
code: string;
message: string;
details?: Record<string, any>;
};
}
async function executeFunction(
functionName: string,
parameters: Record<string, string>
): Promise<FunctionExecutionResult> {
const schema = functionRegistry[functionName];
if (!schema) {
return {
success: false,
error: { code: 'FUNCTION_NOT_FOUND', message: `No schema registered for ${functionName}` },
};
}
try {
const validatedInput = validateInput(schema.input, parameters);
const rawOutput = await executeExternalService(functionName, validatedInput);
const validatedOutput = validateOutput(schema.output, rawOutput);
return { success: true, data: validatedOutput };
} catch (err) {
return {
success: false,
error: {
code: 'EXECUTION_ERROR',
message: err instanceof Error ? err.message : 'Unknown execution error',
details: { functionName, parameters },
},
};
}
}
function validateInput(schema: z.ZodObject<any>, params: Record<string, string>): Record<string, string> {
const result = schema.safeParse(params);
if (!result.success) {
throw new Error(`Input validation failed: ${result.error.message}`);
}
return result.data;
}
The executeFunction method returns a standardized result object. Cognigy.AI dialog flows can inspect success and route to confirmation or error handling nodes accordingly.
Step 5: Implement Caching, Logging, and Metrics
Function definitions rarely change at runtime. Caching reduces registry lookup latency. Logging captures invocation duration, success rates, and error codes for usage analysis.
import pino from 'pino';
const logger = pino({ level: 'info' });
interface CacheEntry<T> {
value: T;
expiresAt: number;
}
class FunctionCache {
private store = new Map<string, CacheEntry<FunctionSchema>>();
private readonly TTL_MS = 5 * 60 * 1000; // 5 minutes
get(key: string): FunctionSchema | undefined {
const entry = this.store.get(key);
if (!entry) return undefined;
if (Date.now() > entry.expiresAt) {
this.store.delete(key);
return undefined;
}
return entry.value;
}
set(key: string, value: FunctionSchema): void {
this.store.set(key, { value, expiresAt: Date.now() + this.TTL_MS });
}
}
const functionCache = new FunctionCache();
async function invokeCognigyFunction(utterance: string): Promise<FunctionExecutionResult> {
const startTime = Date.now();
const cognigyResponse = await extractFunctionIntent(utterance);
const functionTrigger = cognigyResponse.functions[0];
if (!functionTrigger) {
logger.warn({ sessionId: cognigyResponse.sessionId }, 'No function intent detected');
return { success: false, error: { code: 'NO_FUNCTION', message: 'AI did not extract a function call' } };
}
const cachedSchema = functionCache.get(functionTrigger.name);
const schema = cachedSchema || functionRegistry[functionTrigger.name];
if (schema) functionCache.set(functionTrigger.name, schema);
const result = await executeFunction(functionTrigger.name, functionTrigger.parameters);
const duration = Date.now() - startTime;
logger.info(
{
sessionId: cognigyResponse.sessionId,
functionName: functionTrigger.name,
success: result.success,
durationMs: duration,
errorCode: result.error?.code,
},
'Function invocation completed'
);
return result;
}
The cache stores schemas with a 5-minute TTL. The logger records structured JSON with session IDs, function names, success flags, and execution duration. Metrics can be piped to Prometheus, Datadog, or CloudWatch via pino transports.
Step 6: Expose Function Registry for Dynamic Discovery
External systems or admin consoles may need to discover available functions and their schemas. An Express endpoint serves the registry in a machine-readable format.
import express from 'express';
const app = express();
app.get('/api/functions', (req, res) => {
const registryOutput = Object.entries(functionRegistry).map(([name, schema]) => ({
name,
inputSchema: schema.input.describe(),
outputSchema: schema.output.describe(),
cached: !!functionCache.get(name),
}));
res.json({ functions: registryOutput, total: registryOutput.length });
});
app.listen(3000, () => {
logger.info('Function registry service listening on port 3000');
});
The endpoint returns Zod schema descriptions, which include field names, types, constraints, and defaults. Clients can generate client-side validation or documentation dynamically.
Complete Working Example
The following script combines all components into a single runnable module. Replace environment variables with your Cognigy.AI credentials.
import axios, { AxiosInstance } from 'axios';
import { z } from 'zod';
import { v4 as uuidv4 } from 'uuid';
import pino from 'pino';
import express from 'express';
const COGNIGY_TENANT = process.env.COYNIGY_TENANT || 'your-tenant';
const COGNIGY_API_KEY = process.env.COYNIGY_API_KEY || '';
const BASE_URL = `https://${COGNIGY_TENANT}.cognigy.ai/api/v1`;
const logger = pino({ level: 'info' });
const cognigyClient: AxiosInstance = axios.create({
baseURL: BASE_URL,
headers: {
'Content-Type': 'application/json',
'x-api-key': COGNIGY_API_KEY,
},
timeout: 5000,
});
const WeatherSchema = {
input: z.object({ location: z.string().min(1), unit: z.enum(['celsius', 'fahrenheit']).default('celsius') }),
output: z.object({ temperature: z.number(), condition: z.string(), timestamp: z.string().datetime() }),
};
const FlightStatusSchema = {
input: z.object({ flightNumber: z.string().regex(/^([A-Z]{2}\d{3,4})$/), date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/) }),
output: z.object({ status: z.enum(['on-time', 'delayed', 'cancelled']), gate: z.string().nullable(), departureTime: z.string().datetime() }),
};
type FunctionSchema = { input: z.ZodObject<any>; output: z.ZodObject<any>; };
interface FunctionRegistry { [key: string]: FunctionSchema; }
const functionRegistry: FunctionRegistry = {
getWeather: WeatherSchema,
checkFlight: FlightStatusSchema,
};
interface CognigyResponse {
sessionId: string;
intent: { name: string; confidence: number };
functions: Array<{ name: string; parameters: Record<string, string> }>;
entities: Array<{ name: string; value: string; confidence: number }>;
}
async function extractFunctionIntent(utterance: string): Promise<CognigyResponse> {
const payload = { sessionId: uuidv4(), input: utterance, language: 'en' };
const response = await cognigyClient.post<CognigyResponse>('/ai/dialog', payload);
return response.data;
}
async function executeExternalService(functionName: string, parameters: Record<string, string>): Promise<Record<string, any>> {
switch (functionName) {
case 'getWeather':
return { temperature: 72, condition: 'Partly Cloudy', timestamp: new Date().toISOString() };
case 'checkFlight':
return { status: 'on-time', gate: 'B12', departureTime: '2024-06-15T14:30:00Z' };
default:
throw new Error(`Unknown function: ${functionName}`);
}
}
function validateOutput<T>(schema: z.ZodType<T>, data: unknown): T {
const result = schema.safeParse(data);
if (!result.success) throw new Error(`Validation failed: ${result.error.message}`);
return result.data;
}
function validateInput(schema: z.ZodObject<any>, params: Record<string, string>): Record<string, string> {
const result = schema.safeParse(params);
if (!result.success) throw new Error(`Input validation failed: ${result.error.message}`);
return result.data;
}
interface FunctionExecutionResult {
success: boolean;
data?: Record<string, any>;
error?: { code: string; message: string; details?: Record<string, any> };
}
async function executeFunction(functionName: string, parameters: Record<string, string>): Promise<FunctionExecutionResult> {
const schema = functionRegistry[functionName];
if (!schema) return { success: false, error: { code: 'FUNCTION_NOT_FOUND', message: `No schema registered for ${functionName}` } };
try {
const validatedInput = validateInput(schema.input, parameters);
const rawOutput = await executeExternalService(functionName, validatedInput);
const validatedOutput = validateOutput(schema.output, rawOutput);
return { success: true, data: validatedOutput };
} catch (err) {
return { success: false, error: { code: 'EXECUTION_ERROR', message: err instanceof Error ? err.message : 'Unknown execution error', details: { functionName, parameters } } };
}
}
class FunctionCache {
private store = new Map<string, { value: FunctionSchema; expiresAt: number }>();
private readonly TTL_MS = 5 * 60 * 1000;
get(key: string): FunctionSchema | undefined {
const entry = this.store.get(key);
if (!entry) return undefined;
if (Date.now() > entry.expiresAt) { this.store.delete(key); return undefined; }
return entry.value;
}
set(key: string, value: FunctionSchema): void {
this.store.set(key, { value, expiresAt: Date.now() + this.TTL_MS });
}
}
const functionCache = new FunctionCache();
async function invokeCognigyFunction(utterance: string): Promise<FunctionExecutionResult> {
const startTime = Date.now();
const cognigyResponse = await extractFunctionIntent(utterance);
const functionTrigger = cognigyResponse.functions[0];
if (!functionTrigger) {
logger.warn({ sessionId: cognigyResponse.sessionId }, 'No function intent detected');
return { success: false, error: { code: 'NO_FUNCTION', message: 'AI did not extract a function call' } };
}
const cachedSchema = functionCache.get(functionTrigger.name);
const schema = cachedSchema || functionRegistry[functionTrigger.name];
if (schema) functionCache.set(functionTrigger.name, schema);
const result = await executeFunction(functionTrigger.name, functionTrigger.parameters);
const duration = Date.now() - startTime;
logger.info({ sessionId: cognigyResponse.sessionId, functionName: functionTrigger.name, success: result.success, durationMs: duration, errorCode: result.error?.code }, 'Function invocation completed');
return result;
}
const app = express();
app.get('/api/functions', (req, res) => {
const registryOutput = Object.entries(functionRegistry).map(([name, schema]) => ({
name, inputSchema: schema.input.describe(), outputSchema: schema.output.describe(), cached: !!functionCache.get(name),
}));
res.json({ functions: registryOutput, total: registryOutput.length });
});
app.post('/api/invoke', express.json(), async (req, res) => {
const { utterance } = req.body;
if (!utterance) return res.status(400).json({ error: 'utterance is required' });
const result = await invokeCognigyFunction(utterance);
res.json(result);
});
app.listen(3000, () => {
logger.info('Function registry service listening on port 3000');
});
Run the script with ts-node index.ts or compile with tsc and execute node dist/index.js. Send a test request:
curl -X POST http://localhost:3000/api/invoke \
-H "Content-Type: application/json" \
-d '{"utterance": "What is the weather in London in fahrenheit?"}'
Common Errors & Debugging
Error: 401 Unauthorized
What causes it: The x-api-key header is missing, expired, or lacks permissions to call /api/v1/ai/dialog.
How to fix it: Verify the API key in the Cognigy.AI admin console under Settings > API Keys. Ensure the key has ai:read and ai:write permissions. If using OAuth2, confirm the access token includes the cognigy.ai:read scope.
Code showing the fix:
// Verify header attachment
console.log('Auth header:', cognigyClient.defaults.headers.common['x-api-key']);
Error: 400 Bad Request
What causes it: The request body lacks sessionId, input, or language, or the JSON structure is malformed.
How to fix it: Validate the payload against the CognigyRequest interface before sending. Ensure language matches a supported locale code.
Code showing the fix:
const payload = { sessionId: uuidv4(), input: utterance.trim(), language: 'en' };
if (!payload.input) throw new Error('Utterance cannot be empty');
Error: 429 Too Many Requests
What causes it: The Cognigy.AI API enforces rate limits per tenant. Rapid utterance processing triggers throttling.
How to fix it: Implement exponential backoff retry logic. Cognigy.AI returns Retry-After headers when applicable.
Code showing the fix:
async function retryOnRateLimit<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (err: any) {
if (err.response?.status === 429) {
const retryAfter = err.response.headers['retry-after'] || Math.pow(2, attempt);
await new Promise(res => setTimeout(res, retryAfter * 1000));
continue;
}
throw err;
}
}
throw new Error('Max retries exceeded for 429');
}
// Usage: const response = await retryOnRateLimit(() => cognigyClient.post('/ai/dialog', payload));
Error: Zod Validation Failure
What causes it: External service returns a shape that does not match the output schema, or Cognigy.AI extracts missing/invalid parameters.
How to fix it: Log the raw response and adjust the schema to allow nullable fields or wider types when necessary. Use safeParse to catch mismatches without crashing.
Code showing the fix:
const parseResult = schema.output.safeParse(rawOutput);
if (!parseResult.success) {
logger.error({ errors: parseResult.error.errors }, 'Schema mismatch');
throw new Error(`Output validation failed: ${parseResult.error.message}`);
}