Skip to main content

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

  1. Set Unique call_id - Use timestamp + UUID
  2. Handle Timeouts - 5 second max recommended
  3. Retry Logic - 2-3 retries with backoff
  4. Graceful Degradation - Continue if API fails
  5. Cache Responses - Store during interaction
  6. Async Calls - Don't block user experience
  7. 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


Ready to add memory to your custom framework? Sign up →