Custom Framework Integration
Generic REST API integration pattern for custom AI frameworks and applications.
Overview
This guide provides a universal integration pattern that works with any custom AI framework, voice bot, or application that can make HTTP REST calls.
Use this if you're building:
- Custom AI voice agents
- Proprietary contact center solutions
- Home-grown chatbots
- Any application needing caller memory
Core Pattern
1. At Start of Interaction
POST /v1/calls/start
Host: api.stickycalls.com
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
"call_id": "unique_interaction_id",
"identity_hints": {
"ani": "+14155551234",
"external_ids": {
"user_id": "optional_user_id"
}
}
}
2. Parse Response
{
"call_id": "unique_interaction_id",
"customer_ref": "cust_abc123",
"call_start": "2026-02-06T15:30:00.000Z",
"identity": {
"confidence": 0.95,
"level": "very_high",
"sources": ["ani:mobile", "external_id"],
"recommendation": "reuse"
},
"open_intents": [{
"intent": "follow_up_required",
"status": "open",
"attempt_count": 1
}],
"variables": {
"summary": {
"value": "Customer previously called about...",
"source": null,
"ttl_seconds": 2592000
}
}
}
3. Use Context in Your Logic
const identity = response.identity;
if (identity.confidence >= 0.7) {
// Personalize interaction
const summary = response.variables?.summary?.value || '';
greeting = `Welcome back! ${summary}`;
route_to = determineRoute(response.open_intents);
} else {
// Standard flow
greeting = "Welcome! How can I help?";
route_to = "general";
}
4. At End of Interaction
POST /v1/calls/end
Host: api.stickycalls.com
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
"call_id": "same_interaction_id",
"customer_ref": "cust_abc123",
"intent": "general_inquiry",
"intent_status": "open",
"variables": {
"summary": "Summary of conversation",
"disposition": "resolved"
}
}
Implementation Examples
Python Framework
import requests
class StickyCallsMemory:
def __init__(self, api_key):
self.api_key = api_key
self.base_url = "https://api.stickycalls.com"
def get_context(self, call_id, phone_number):
"""Get caller context at start"""
response = requests.post(
f"{self.base_url}/v1/calls/start",
headers={
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
},
json={
"call_id": call_id,
"identity_hints": {
"ani": phone_number
}
}
)
return response.json()
def save_context(self, call_id, customer_ref, intent, summary, status="closed"):
"""Save context at end"""
response = requests.post(
f"{self.base_url}/v1/calls/end",
headers={
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
},
json={
"call_id": call_id,
"customer_ref": customer_ref,
"intent": intent,
"intent_status": status,
"variables": {
"summary": summary
}
}
)
return response.json()
# Usage
memory = StickyCallsMemory("sk_test_abc123")
# At call start
context = memory.get_context("call_001", "+14155551234")
identity = context.get('identity', {})
if identity.get('confidence', 0) >= 0.7:
summary = context.get('variables', {}).get('summary', {}).get('value', '')
print(f"Welcome back! Context: {summary}")
else:
print("Welcome! First time caller.")
# At call end
memory.save_context("call_001", context.get('customer_ref'), "support_inquiry", "Issue resolved successfully")
Node.js Framework
const axios = require('axios');
class StickyCallsMemory {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseUrl = 'https://api.stickycalls.com';
}
async getContext(callId, phoneNumber) {
const response = await axios.post(
`${this.baseUrl}/v1/calls/start`,
{
call_id: callId,
identity_hints: {
ani: phoneNumber
}
},
{
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
}
}
);
return response.data;
}
async saveContext(callId, customerRef, intent, summary, status = 'closed') {
const response = await axios.post(
`${this.baseUrl}/v1/calls/end`,
{
call_id: callId,
customer_ref: customerRef,
intent: intent,
intent_status: status,
variables: {
summary: summary
}
},
{
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
}
}
);
return response.data;
}
}
// Usage
const memory = new StickyCallsMemory('sk_test_abc123');
// At call start
const context = await memory.getContext('call_001', '+14155551234');
const identity = context.identity || {};
if (identity.confidence >= 0.7) {
const summary = context.variables?.summary?.value || '';
console.log(`Welcome back! Context: ${summary}`);
}
// At call end
await memory.saveContext('call_001', context.customer_ref, 'support_inquiry', 'Issue resolved');
Ruby Framework
require 'httparty'
class StickyCallsMemory
include HTTParty
base_uri 'https://api.stickycalls.com'
def initialize(api_key)
@api_key = api_key
@headers = {
'Authorization' => "Bearer #{api_key}",
'Content-Type' => 'application/json'
}
end
def get_context(call_id, phone_number)
self.class.post(
'/v1/calls/start',
headers: @headers,
body: {
call_id: call_id,
identity_hints: {
ani: phone_number
}
}.to_json
)
end
def save_context(call_id, customer_ref, intent, summary, status = 'closed')
self.class.post(
'/v1/calls/end',
headers: @headers,
body: {
call_id: call_id,
customer_ref: customer_ref,
intent: intent,
intent_status: status,
variables: {
summary: summary
}
}.to_json
)
end
end
# Usage
memory = StickyCallsMemory.new('sk_test_abc123')
# At call start
context = memory.get_context('call_001', '+14155551234')
identity = context['identity'] || {}
if identity['confidence'].to_f >= 0.7
summary = context.dig('variables', 'summary', 'value') || ''
puts "Welcome back! Context: #{summary}"
end
# At call end
memory.save_context('call_001', context['customer_ref'], 'support_inquiry', 'Issue resolved')
Design Patterns
Pattern 1: Async Context Retrieval
Don't block main flow:
async function handleCall(phoneNumber) {
// Start context retrieval (don't wait)
const contextPromise = getContext(phoneNumber);
// Play initial greeting while fetching
await playGreeting("Please wait...");
// Now wait for context
const context = await contextPromise;
// Personalize based on result
if (context.match_found) {
await playMessage(`Welcome back! ${context.context}`);
}
}
Pattern 2: Confidence-Based Actions
const actions = {
high: (ctx) => {
// confidence > 0.9 - full personalization
const summary = ctx.variables?.summary?.value || '';
const firstIntent = ctx.open_intents?.[0]?.intent || 'general';
return {
greeting: `Welcome back! ${summary}`,
route: firstIntent,
priority: 'high'
};
},
medium: (ctx) => {
// confidence 0.5-0.9 - show agent but verify
const summary = ctx.variables?.summary?.value || '';
return {
greeting: "Welcome back! Let me verify your account",
route: 'verification',
showContext: summary
};
},
low: (ctx) => {
// confidence < 0.5 - standard flow
return {
greeting: "Welcome! How can I help?",
route: 'general'
};
}
};
function handleContext(context) {
const confidence = context.identity?.confidence || 0;
if (confidence > 0.9) return actions.high(context);
if (confidence > 0.5) return actions.medium(context);
return actions.low(context);
}
Pattern 3: Progressive Context Building
Build context throughout interaction:
class ConversationContext {
constructor() {
this.summary = [];
}
addExchange(question, answer) {
this.summary.push(`Q: ${question}, A: ${answer}`);
}
addResolution(resolution) {
this.summary.push(`Resolution: ${resolution}`);
}
getSummary() {
return this.summary.join('. ');
}
}
// During conversation
const ctx = new ConversationContext();
ctx.addExchange("What's your issue?", "Billing problem");
ctx.addExchange("What charge?", "Duplicate $50");
ctx.addResolution("Refund processed");
// At end
await saveContext(callId, customerRef, 'billing_refund', ctx.getSummary(), 'closed');
Error Handling
async function robustGetContext(callId, phoneNumber) {
const MAX_RETRIES = 2;
const TIMEOUT = 5000;
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
try {
const response = await Promise.race([
getContext(callId, phoneNumber),
timeout(TIMEOUT)
]);
return response;
} catch (error) {
if (attempt === MAX_RETRIES) {
// Failed after retries - return empty context
console.error('Sticky Calls API failed:', error);
return {
call_id: callId,
customer_ref: null,
identity: {
confidence: 0,
level: 'none',
sources: [],
recommendation: 'create'
},
variables: {},
open_intents: []
};
}
// Wait before retry
await sleep(1000);
}
}
}
Best Practices
- Set Unique call_id - Use timestamp + UUID
- Handle Timeouts - 5 second max recommended
- Retry Logic - 2-3 retries with backoff
- Graceful Degradation - Continue if API fails
- Cache Responses - Store during interaction
- Async Calls - Don't block user experience
- Log Everything - Track API calls for debugging
Testing
// Test script
async function testIntegration() {
const memory = new StickyCallsMemory('sk_test_abc123');
// Test 1: First call (no match expected)
console.log('Test 1: First call');
const result1 = await memory.getContext('test_001', '+14155559999');
console.assert(result1.identity.confidence < 0.5, 'Should not match on first call');
// Test 2: Save context
console.log('Test 2: Save context');
await memory.saveContext('test_001', result1.customer_ref, 'test_inquiry', 'Test context saved');
// Test 3: Second call (match expected)
console.log('Test 3: Second call');
const result2 = await memory.getContext('test_002', '+14155559999');
console.assert(result2.identity.confidence >= 0.7, 'Should match on second call');
const summary = result2.variables?.summary?.value || '';
console.assert(summary.includes('Test context'), 'Context should match');
console.log('All tests passed!');
}
Resources
- API Reference - Complete endpoint docs
- Best Practices - Production tips
- Generic IVR Guide - Similar patterns
- Contact Support - Get help
Ready to add memory to your custom framework? Sign up →