Implementing Customer Lookup with Architect GetExternalContactAction
What You Will Build
- This tutorial demonstrates how to configure a Genesys Cloud CX Architect flow to retrieve customer data from an external API based on the caller’s phone number.
- This solution uses the Genesys Cloud CX Architect SDK (JavaScript/TypeScript) and the purecloud-platform-client-v2 Python SDK for validation.
- The programming languages covered are JavaScript (for the Architect flow definition) and Python (for the external mock API service).
Prerequisites
- OAuth Client Type: API User or Application Client with the
architect:flow:readandarchitect:flow:writescopes. - External Service: A publicly accessible HTTPS endpoint that accepts a phone number and returns JSON customer data.
- SDK Version:
@genesyscloud/purecloud-platform-client-v2(latest stable) or Pythonpurecloud-platform-client-v2(latest stable). - Language/Runtime: Node.js 18+ for Architect SDK development; Python 3.9+ for the external API mock.
- Dependencies:
- npm:
@genesyscloud/purecloud-platform-client-v2,axios(for external calls) - pip:
purecloud-platform-client-v2,fastapi,uvicorn(for mock service)
- npm:
Authentication Setup
Before interacting with the Architect API, you must obtain a valid OAuth 2.0 access token. The following Python example uses the purecloud-platform-client-v2 SDK to handle the authentication flow. This token is required for all subsequent SDK calls in this tutorial.
import os
from purecloudplatformclientv2.rest import ApiException
from purecloudplatformclientv2 import PlatformApi, Configuration
def get_access_token():
"""
Retrieves an OAuth access token using client credentials flow.
"""
# Initialize the configuration with your client ID and secret
config = Configuration()
config.host = "https://api.mypurecloud.com"
config.access_token = None # We will set this after authentication
# Create the Platform API client
platform_api = PlatformApi(configuration=config)
try:
# Authenticate using client credentials
# Replace with your actual Client ID and Secret
client_id = os.getenv("GENESYS_CLIENT_ID")
client_secret = os.getenv("GENESYS_CLIENT_SECRET")
if not client_id or not client_secret:
raise ValueError("GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET environment variables are required.")
auth_response = platform_api.post_oauth_token(
grant_type="client_credentials",
client_id=client_id,
client_secret=client_secret
)
config.access_token = auth_response.access_token
return config
except ApiException as e:
print(f"Authentication failed: {e.reason}")
raise
if __name__ == "__main__":
# Initialize the authenticated configuration
auth_config = get_access_token()
print(f"Successfully authenticated. Token expires in {auth_config.access_token} seconds.")
Ensure you store your GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET in environment variables. Never hardcode credentials in source code. The PlatformApi handles the token exchange. You will pass this auth_config object to the ArchitectApi client later.
Implementation
Step 1: Define the External API Contract
The GetExternalContactAction makes an HTTP POST request to a specified URL. You must define what this endpoint expects and what it returns. The action sends a JSON body containing the contact details. By default, it includes the phoneNumber if the flow is initiated by an inbound call.
Create a simple FastAPI service to simulate your CRM lookup. This service will receive the phone number and return mock customer data.
# external_crm_api.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import Optional
app = FastAPI()
class ContactRequest(BaseModel):
"""
Genesys Architect sends this structure to the external endpoint.
The 'phoneNumber' field is populated by the GetExternalContactAction
if configured to pass the caller ID.
"""
phoneNumber: Optional[str] = None
# Other fields may be included based on flow context, e.g., 'email', 'name'
class CustomerData(BaseModel):
"""
The response structure expected by Genesys.
This data will be available as variables in the subsequent flow steps.
"""
customer_id: str
name: str
loyalty_points: int
is_vip: bool
@app.post("/api/v1/lookup")
def lookup_customer(contact: ContactRequest):
"""
Simulates a database lookup by phone number.
"""
if not contact.phoneNumber:
raise HTTPException(status_code=400, detail="Phone number is required")
# Clean phone number format for lookup (remove spaces, dashes)
clean_phone = contact.phoneNumber.replace(" ", "").replace("-", "")
# Mock database
mock_db = {
"15551234567": {
"customer_id": "CUST-001",
"name": "Alice Smith",
"loyalty_points": 1500,
"is_vip": True
},
"15559876543": {
"customer_id": "CUST-002",
"name": "Bob Jones",
"loyalty_points": 200,
"is_vip": False
}
}
# Lookup logic
if clean_phone in mock_db:
return mock_db[clean_phone]
else:
# Return a default structure or raise 404.
# Genesys can handle 404s, but returning a default object is often safer for flow stability.
return {
"customer_id": "UNKNOWN",
"name": "Unknown Customer",
"loyalty_points": 0,
"is_vip": False
}
# Run with: uvicorn external_crm_api:app --host 0.0.0.0 --port 8000
Deploy this service to a public HTTPS endpoint. For local testing, you can use a tunneling service like ngrok. The endpoint must be accessible from the Genesys Cloud infrastructure. Note that Genesys Cloud does not support HTTP endpoints for external actions; HTTPS is mandatory.
Step 2: Configure the GetExternalContactAction
The GetExternalContactAction is defined within an Architect Flow. The action requires a URL, a timeout, and optionally, a mapping of input variables. When the action executes, it sends a POST request to the URL. The response body is parsed as JSON and mapped to flow variables.
Below is a JavaScript example using the @genesyscloud/purecloud-platform-client-v2 SDK to create a flow with this action. This example assumes you have already authenticated and obtained an ApiClient instance.
const { ArchitectApi, Flow, GetExternalContactAction } = require('@genesyscloud/purecloud-platform-client-v2');
const { ApiClient } = require('@genesyscloud/purecloud-platform-client-v2');
async function createFlowWithExternalLookup(apiClient) {
const architectApi = new ArchitectApi(apiClient);
// Define the external API endpoint
const externalUrl = "https://your-ngrok-url.ngrok.io/api/v1/lookup";
// Define the GetExternalContactAction
const lookupAction = new GetExternalContactAction({
name: "Lookup Customer",
description: "Retrieves customer data from external CRM",
url: externalUrl,
timeout: 10000, // 10 seconds
// Optional: Map input variables.
// By default, phoneNumber is passed if available in the context.
// You can explicitly map other variables if needed.
// inputVariables: {
// phoneNumber: "$phoneNumber"
// }
});
// Define a subsequent action to use the retrieved data
// This is a simple SetVariable action to demonstrate data availability
const setVariableAction = new SetVariableAction({
name: "Set Customer Name",
description: "Stores the retrieved customer name",
set: {
// The response from the external API is available as a map.
// Access fields using dot notation.
customerName: "$lookupResponse.name"
}
});
// Create the flow definition
const flow = new Flow({
name: "Customer Lookup Flow",
description: "Demonstrates GetExternalContactAction",
type: "voice",
initialAction: {
name: "Start",
type: "Start",
routing: {
queue: {
id: "your-queue-id" // Replace with a valid queue ID
}
},
on: {
// Chain the actions
next: lookupAction.name,
// Handle errors from the external call
onerror: {
name: "Handle Error",
type: "SetVariable",
set: {
errorStatus: "$error.status",
errorMessage: "$error.message"
},
on: {
next: setVariableAction.name
}
}
},
ontimeout: {
name: "Handle Timeout",
type: "SetVariable",
set: {
errorStatus: "Timeout",
errorMessage: "External API did not respond in time"
},
on: {
next: setVariableAction.name
}
}
}
});
// Add the actions to the flow
flow.actions = [
lookupAction,
setVariableAction
];
try {
// Publish the flow
const response = await architectApi.postArchitectFlow({
body: flow
});
console.log(`Flow created successfully. Flow ID: ${response.id}`);
return response.id;
} catch (error) {
console.error("Failed to create flow:", error);
throw error;
}
}
// Usage
// const apiClient = ApiClient.instance;
// apiClient.authSettings['OAuth2'].clientId = 'your-client-id';
// apiClient.authSettings['OAuth2'].clientSecret = 'your-client-secret';
// createFlowWithExternalLookup(apiClient).then(id => console.log(id));
The GetExternalContactAction sends the following HTTP request to your external endpoint:
POST /api/v1/lookup HTTP/1.1
Host: your-ngrok-url.ngrok.io
Content-Type: application/json
{
"phoneNumber": "+15551234567"
}
The response from your external API is automatically parsed by Genesys. If your API returns:
{
"customer_id": "CUST-001",
"name": "Alice Smith",
"loyalty_points": 1500,
"is_vip": true
}
You can access these fields in subsequent flow steps using the syntax $lookupResponse.customer_id, $lookupResponse.name, etc. The variable lookupResponse is the default name for the response object of a GetExternalContactAction. You can change this by setting the outputVariable property on the action.
Step 3: Handle Errors and Timeouts
External API calls can fail due to network issues, invalid data, or service outages. The GetExternalContactAction supports error handling through the onerror and ontimeout properties.
In the previous example, the onerror block captures the HTTP status code and message from the external API. The ontimeout block handles cases where the external API does not respond within the specified timeout period.
It is critical to design your flow to handle these scenarios gracefully. For example, you might route the call to a default queue if the customer lookup fails, or play a message informing the caller that their information could not be retrieved.
The error object contains the following properties:
status: The HTTP status code (e.g., 400, 404, 500).message: The error message returned by the external API or Genesys.headers: The response headers from the external API.
You can use these properties to log errors or make decisions in the flow. For example, if the status is 404, you might assume the customer is new and route them to a sales queue.
Complete Working Example
The following Python script combines authentication, flow creation, and external API validation into a single executable module. This script creates a flow with a GetExternalContactAction and prints the flow ID.
import os
import sys
from purecloudplatformclientv2 import (
PlatformApi,
ArchitectApi,
Flow,
GetExternalContactAction,
SetVariableAction,
Configuration,
ApiException
)
from purecloudplatformclientv2.rest import ApiException
def get_authenticated_api_client():
"""
Returns an authenticated ApiClient instance.
"""
config = Configuration()
config.host = "https://api.mypurecloud.com"
platform_api = PlatformApi(configuration=config)
client_id = os.getenv("GENESYS_CLIENT_ID")
client_secret = os.getenv("GENESYS_CLIENT_SECRET")
if not client_id or not client_secret:
raise ValueError("GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET environment variables are required.")
try:
auth_response = platform_api.post_oauth_token(
grant_type="client_credentials",
client_id=client_id,
client_secret=client_secret
)
config.access_token = auth_response.access_token
return config
except ApiException as e:
print(f"Authentication failed: {e.reason}")
sys.exit(1)
def create_customer_lookup_flow(config):
"""
Creates an Architect flow with a GetExternalContactAction.
"""
architect_api = ArchitectApi(configuration=config)
# Define the external API endpoint
external_url = os.getenv("EXTERNAL_API_URL", "https://example.com/api/v1/lookup")
queue_id = os.getenv("GENESYS_QUEUE_ID", "your-queue-id")
if queue_id == "your-queue-id":
raise ValueError("GENESYS_QUEUE_ID environment variable must be set to a valid queue ID.")
# Define the GetExternalContactAction
lookup_action = GetExternalContactAction(
name="Lookup Customer",
description="Retrieves customer data from external CRM",
url=external_url,
timeout=10000, # 10 seconds
output_variable="customer_data" # Custom output variable name
)
# Define a subsequent action to use the retrieved data
set_variable_action = SetVariableAction(
name="Set Customer Name",
description="Stores the retrieved customer name",
set={
"customerName": "$customer_data.name"
}
)
# Create the flow definition
flow = Flow(
name="Customer Lookup Flow",
description="Demonstrates GetExternalContactAction",
type="voice",
initial_action={
"name": "Start",
"type": "Start",
"routing": {
"queue": {
"id": queue_id
}
},
"on": {
"next": lookup_action.name,
"onerror": {
"name": "Handle Error",
"type": "SetVariable",
"set": {
"errorStatus": "$error.status",
"errorMessage": "$error.message"
},
"on": {
"next": set_variable_action.name
}
},
"ontimeout": {
"name": "Handle Timeout",
"type": "SetVariable",
"set": {
"errorStatus": "Timeout",
"errorMessage": "External API did not respond in time"
},
"on": {
"next": set_variable_action.name
}
}
}
}
)
# Add the actions to the flow
flow.actions = [
lookup_action,
set_variable_action
]
try:
# Publish the flow
response = architect_api.post_architect_flow(body=flow)
print(f"Flow created successfully. Flow ID: {response.id}")
return response.id
except ApiException as e:
print(f"Failed to create flow: {e.reason}")
raise
if __name__ == "__main__":
config = get_authenticated_api_client()
flow_id = create_customer_lookup_flow(config)
This script requires the following environment variables:
GENESYS_CLIENT_ID: Your Genesys Cloud OAuth client ID.GENESYS_CLIENT_SECRET: Your Genesys Cloud OAuth client secret.EXTERNAL_API_URL: The URL of your external CRM lookup endpoint.GENESYS_QUEUE_ID: The ID of the queue to which the flow should route calls.
Run the script with python create_flow.py. Ensure your external API is running and accessible.
Common Errors & Debugging
Error: 401 Unauthorized
What causes it:
The OAuth token is invalid, expired, or missing. This can happen if the token was not refreshed or if the client credentials are incorrect.
How to fix it:
Ensure your GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET are correct. Check that the token is being passed in the Authorization header of the SDK requests. The SDK handles token refresh automatically if the configuration is set up correctly.
Code showing the fix:
Verify the authentication step in get_authenticated_api_client(). Ensure the access_token is set on the Configuration object.
# Correct authentication setup
config.access_token = auth_response.access_token
Error: 400 Bad Request
What causes it:
The external API returned a 400 status code. This usually means the request body was invalid or missing required fields.
How to fix it:
Check the errorMessage variable in your flow. Ensure your external API expects the phoneNumber field in the request body. Verify that the phone number format matches what your API expects.
Code showing the fix:
In your external API, log the incoming request body to debug.
@app.post("/api/v1/lookup")
def lookup_customer(contact: ContactRequest):
print(f"Received request: {contact.json()}")
if not contact.phoneNumber:
raise HTTPException(status_code=400, detail="Phone number is required")
# ...
Error: 504 Gateway Timeout
What causes it:
The external API did not respond within the timeout period specified in the GetExternalContactAction. The default timeout is 10 seconds.
How to fix it:
Increase the timeout property on the GetExternalContactAction if your external API is slow. Alternatively, optimize your external API to respond faster.
Code showing the fix:
const lookupAction = new GetExternalContactAction({
name: "Lookup Customer",
url: externalUrl,
timeout: 30000, // 30 seconds
// ...
});
Error: Flow Validation Error
What causes it:
The flow definition is invalid. This can happen if the action names do not match, if the routing configuration is incorrect, or if the external URL is not HTTPS.
How to fix it:
Check the error message returned by the postArchitectFlow API call. Ensure that all action names are unique and correctly referenced in the on blocks. Verify that the external URL starts with https://.