Skip to content

Integrate External APIs

Learn how to integrate external APIs into your Pika agents, enabling them to access real-time data, third-party services, and external systems.

By the end of this guide, you will:

  • Create Lambda functions that call external APIs
  • Configure API authentication and headers
  • Handle API responses and errors
  • Pass data between agents and APIs
  • Implement rate limiting and caching
  • Follow security best practices
  • A running Pika installation
  • Understanding of REST APIs
  • Familiarity with Lambda functions
  • API credentials for your external service

External APIs extend your agent's capabilities by connecting to:

  • Third-party Services: Weather, maps, payment processors
  • Internal Systems: CRM, inventory, databases
  • Real-time Data: Stock prices, news feeds, sports scores
  • Cloud Services: AWS, Google Cloud, Azure services

Build a Lambda function that calls your external API.

src/tools/weather-api.ts
import { ToolExecutionParams, ToolResponse } from 'pika-shared/types/chatbot/chatbot-types';
interface WeatherInput {
location: string;
unit?: 'celsius' | 'fahrenheit';
}
export async function handler(event: ToolExecutionParams): Promise<ToolResponse> {
try {
const input = event.toolInput as WeatherInput;
const location = input.location;
const unit = input.unit || 'fahrenheit';
// Call external weather API
const apiKey = process.env.WEATHER_API_KEY;
const response = await fetch(
`https://api.weather.com/v1/current?location=${encodeURIComponent(location)}&units=${unit}&apiKey=${apiKey}`
);
if (!response.ok) {
throw new Error(`Weather API returned ${response.status}`);
}
const data = await response.json();
// Return formatted result
return {
toolExecutionSucceeded: true,
responseFromTool: JSON.stringify({
location: data.location,
temperature: data.temp,
condition: data.condition,
humidity: data.humidity,
windSpeed: data.wind.speed
})
};
} catch (error) {
console.error('Weather API error:', error);
return {
toolExecutionSucceeded: false,
responseFromTool: JSON.stringify({
error: error.message || 'Failed to fetch weather data'
})
};
}
}

Handle different authentication methods securely.

const apiKey = process.env.API_KEY;
const response = await fetch(apiUrl, {
headers: {
'X-API-Key': apiKey
}
});
const accessToken = process.env.OAUTH_ACCESS_TOKEN;
const response = await fetch(apiUrl, {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
const username = process.env.API_USERNAME;
const password = process.env.API_PASSWORD;
const auth = Buffer.from(`${username}:${password}`).toString('base64');
const response = await fetch(apiUrl, {
headers: {
'Authorization': `Basic ${auth}`
}
});
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';
async function getApiCredentials() {
const client = new SecretsManagerClient({ region: 'us-east-1' });
const response = await client.send(
new GetSecretValueCommand({ SecretId: 'my-api-credentials' })
);
return JSON.parse(response.SecretString);
}
export async function handler(event: ToolExecutionParams): Promise<ToolResponse> {
const credentials = await getApiCredentials();
const response = await fetch(apiUrl, {
headers: {
'Authorization': `Bearer ${credentials.accessToken}`
}
});
// ...
}

Process and format API responses for agents.

export async function handler(event: ToolExecutionParams): Promise<ToolResponse> {
try {
const response = await fetch(apiUrl);
const rawData = await response.json();
// Transform to agent-friendly format
const structured = {
summary: `Found ${rawData.results.length} items`,
items: rawData.results.map(item => ({
id: item.id,
name: item.title,
price: item.price,
available: item.in_stock
}))
};
return {
toolExecutionSucceeded: true,
responseFromTool: JSON.stringify(structured)
};
} catch (error) {
return {
toolExecutionSucceeded: false,
responseFromTool: JSON.stringify({ error: error.message })
};
}
}
async function parseApiResponse(response: Response) {
const contentType = response.headers.get('content-type');
if (contentType?.includes('application/json')) {
return await response.json();
} else if (contentType?.includes('text/xml')) {
const text = await response.text();
// Parse XML (use xml2js or similar)
return parseXml(text);
} else {
return await response.text();
}
}

Handle API failures gracefully.

export async function handler(event: ToolExecutionParams): Promise<ToolResponse> {
try {
const response = await fetch(apiUrl, {
headers: { 'Authorization': `Bearer ${process.env.API_KEY}` }
});
// Handle HTTP errors
if (!response.ok) {
if (response.status === 401) {
return {
toolExecutionSucceeded: false,
responseFromTool: JSON.stringify({
error: 'API authentication failed. Please check credentials.'
})
};
} else if (response.status === 429) {
return {
toolExecutionSucceeded: false,
responseFromTool: JSON.stringify({
error: 'API rate limit exceeded. Please try again later.'
})
};
} else if (response.status >= 500) {
return {
toolExecutionSucceeded: false,
responseFromTool: JSON.stringify({
error: 'External service is temporarily unavailable.'
})
};
} else {
return {
toolExecutionSucceeded: false,
responseFromTool: JSON.stringify({
error: `API error: ${response.status} ${response.statusText}`
})
};
}
}
const data = await response.json();
// Validate response data
if (!data || typeof data !== 'object') {
throw new Error('Invalid API response format');
}
return {
toolExecutionSucceeded: true,
responseFromTool: JSON.stringify(data)
};
} catch (error) {
console.error('API integration error:', error);
// Network errors
if (error.name === 'TypeError' && error.message.includes('fetch')) {
return {
toolExecutionSucceeded: false,
responseFromTool: JSON.stringify({
error: 'Unable to connect to external service.'
})
};
}
// Timeout errors
if (error.name === 'AbortError') {
return {
toolExecutionSucceeded: false,
responseFromTool: JSON.stringify({
error: 'Request timed out. Please try again.'
})
};
}
// Generic error
return {
toolExecutionSucceeded: false,
responseFromTool: JSON.stringify({
error: 'An unexpected error occurred.'
})
};
}
}

Step 5: Implement Rate Limiting and Caching

Section titled “Step 5: Implement Rate Limiting and Caching”

Optimize API usage and reduce costs.

const cache = new Map<string, { data: any; timestamp: number }>();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
export async function handler(event: ToolExecutionParams): Promise<ToolResponse> {
const input = event.toolInput as { location: string };
const cacheKey = `weather:${input.location}`;
// Check cache
const cached = cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
return {
toolExecutionSucceeded: true,
responseFromTool: cached.data
};
}
// Call API
const response = await fetch(apiUrl);
const data = await response.json();
const result = JSON.stringify(data);
// Update cache
cache.set(cacheKey, { data: result, timestamp: Date.now() });
return {
toolExecutionSucceeded: true,
responseFromTool: result
};
}
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, GetCommand, PutCommand } from '@aws-sdk/lib-dynamodb';
const client = DynamoDBDocumentClient.from(new DynamoDBClient({}));
const CACHE_TABLE = process.env.CACHE_TABLE_NAME;
const CACHE_TTL = 300; // 5 minutes
async function getCachedResult(key: string) {
const result = await client.send(new GetCommand({
TableName: CACHE_TABLE,
Key: { cacheKey: key }
}));
if (!result.Item) return null;
const ttl = result.Item.ttl as number;
if (Date.now() / 1000 > ttl) return null;
return result.Item.data;
}
async function setCachedResult(key: string, data: any) {
await client.send(new PutCommand({
TableName: CACHE_TABLE,
Item: {
cacheKey: key,
data: data,
ttl: Math.floor(Date.now() / 1000) + CACHE_TTL
}
}));
}

Register the tool with your agent.

import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as nodejs from 'aws-cdk-lib/aws-lambda-nodejs';
// Create Lambda function
const weatherApiFunction = new nodejs.NodejsFunction(this, 'WeatherApiFunction', {
entry: 'src/tools/weather-api.ts',
handler: 'handler',
runtime: lambda.Runtime.NODEJS_22_X,
timeout: cdk.Duration.seconds(30),
environment: {
WEATHER_API_KEY: process.env.WEATHER_API_KEY!
}
});
// Register tool with agent
const agentData: AgentDataRequest = {
userId: `cloudformation/${this.stackName}`,
agent: {
agentId: 'weather-agent',
basePrompt: 'You are a weather assistant...',
modelId: 'anthropic.claude-3-5-sonnet-20241022-v2:0'
},
tools: [{
toolId: 'weather-api-tool',
name: 'get_weather',
displayName: 'Weather API',
description: 'Get current weather for any location',
executionType: 'lambda',
lambdaArn: 'WILL_BE_REPLACED_BY_CUSTOM_RESOURCE',
functionSchema: {
name: 'get_weather',
description: 'Get current weather information',
inputSchema: {
type: 'object',
properties: {
location: {
type: 'string',
description: 'City name or coordinates'
}
},
required: ['location']
}
}
}]
};
  • Never hardcode credentials in code
  • Store secrets in AWS Secrets Manager or Systems Manager
  • Use IAM roles for AWS service access
  • Validate and sanitize all inputs
  • Implement request signing when available
  • Implement exponential backoff for retries
  • Set appropriate timeouts
  • Handle partial failures gracefully
  • Log errors for debugging
  • Monitor API health
  • Cache frequently requested data
  • Use connection pooling
  • Implement request batching when possible
  • Compress large payloads
  • Monitor latency and optimize
  • Cache responses to reduce API calls
  • Use appropriate Lambda memory settings
  • Implement request throttling
  • Monitor API usage and costs
  • Consider reserved capacity for high-volume APIs