API Reference
Complete reference for Sticky Calls API endpoints.
Table of Contents
Base URL
https://api.stickycalls.com
Authentication
All API requests require Bearer token authentication:
Authorization: Bearer YOUR_API_KEY
Get your API key from the dashboard.
Endpoints
POST /v1/calls/start
Start a new call and identify the caller.
Request:
curl -X POST https://api.stickycalls.com/v1/calls/start \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"call_id": "call_001",
"identity_hints": {
"ani": "+14155551234",
"external_ids": {
"customer_id": "12345"
}
}
}'
Request Body Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
call_id | string | Yes | Unique identifier for this call (max 128 chars) |
identity_hints | object | Yes | Caller identification information |
identity_hints.ani | string | No* | Caller's phone number in E.164 format |
identity_hints.dnis | string | No | Dialed number (your phone number they called) |
identity_hints.external_ids | object | No* | Key-value pairs of external IDs (CRM ID, account number, etc.) |
identity_hints.customer_ref | string | No* | Previously returned customer_ref UUID |
telco | object | No | Telco lookup data for better confidence scoring |
telco.line_type | enum | No | One of: mobile, landline, voip, unknown |
*At least one of ani, external_ids, or customer_ref must be provided.
Response (Returning Customer):
{
"call_id": "call_001",
"customer_ref": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"call_start": "2026-02-07T10:30:00.000Z",
"identity": {
"confidence": 0.9,
"level": "very_high",
"sources": ["ani:mobile", "external_id", "recency:1day"],
"recommendation": "reuse"
},
"open_intents": [
{
"intent": "refund_request",
"status": "open",
"attempt_count": 2
}
],
"variables": {
"name": {
"value": "John Doe",
"source": null,
"ttl_seconds": 2592000
},
"email": {
"value": "john@example.com",
"source": null,
"ttl_seconds": 2592000
},
"account_balance": {
"value": "$1,234.56",
"source": null,
"ttl_seconds": 2592000
}
}
}
Response (New Customer):
{
"call_id": "call_001",
"customer_ref": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"call_start": "2026-02-07T10:30:00.000Z",
"identity": {
"confidence": 0,
"level": "low",
"sources": [],
"recommendation": "ignore"
},
"open_intents": [],
"variables": {}
}
Response Fields:
| Field | Type | Description |
|---|---|---|
call_id | string | Echo of the request call_id |
customer_ref | string | UUID for this customer (use in /calls/end) |
call_start | string | ISO 8601 timestamp when call started |
identity.confidence | number | 0.0 to 1.0 confidence score |
identity.level | enum | low, medium, high, or very_high |
identity.sources | array | Confidence factors (e.g., ["ani:mobile", "external_id"]) |
identity.recommendation | enum | reuse (≥0.5), confirm (0.3-0.5), or ignore (<0.3) |
open_intents | array | Unresolved intents from previous calls |
variables | object | Customer context (name, preferences, account info, etc.) |
Confidence Recommendations:
reuse(≥0.5): Automatically use customer context without verificationconfirm(0.3-0.5): Ask customer to verify identity before using contextignore(<0.3): Treat as new customer, don't use context
POST /v1/calls/end
End a call and save context for future interactions.
Request (Simple Form):
curl -X POST https://api.stickycalls.com/v1/calls/end \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"call_id": "call_001",
"customer_ref": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"intent": "billing_inquiry",
"intent_status": "resolved",
"variables": {
"name": "John Doe",
"email": "john@example.com",
"last_interaction": "Resolved billing issue - refunded $50"
}
}'
Request Body Parameters (Simple Form):
| Field | Type | Required | Description |
|---|---|---|---|
call_id | string | Yes | Must match call_id from /calls/start |
customer_ref | string | No* | UUID from /calls/start response |
identity_hints | object | No* | Same structure as /calls/start |
intent | string | No | Primary topic discussed (max 128 chars) |
intent_status | enum | No | open (unresolved) or resolved (completed) |
variables | object | No | Key-value pairs to save (keys max 128 chars, values max 1024 chars) |
*Either customer_ref or identity_hints must be provided.
Request (Advanced Form with Multiple Intents):
{
"call_id": "call_001",
"customer_ref": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"intent_updates": [
{
"intent": "refund_request",
"status": "resolved",
"resolution": {
"type": "processed",
"external_reference": "refund_12345"
}
},
{
"intent": "account_upgrade",
"status": "open"
}
],
"variable_updates": {
"name": {
"value": "John Doe",
"source": "agent_collected",
"ttl_seconds": 7776000
},
"vip_tier": {
"value": "Gold",
"source": "crm_sync",
"ttl_seconds": 2592000
}
}
}
Response:
{
"call_id": "call_001",
"customer_ref": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"call_start": "2026-02-07T10:30:00.000Z",
"call_end": "2026-02-07T10:35:30.000Z",
"duration_seconds": 330,
"intents_updated": 2,
"variables_updated": 5
}
Response Fields:
| Field | Type | Description |
|---|---|---|
call_id | string | Echo of the request call_id |
customer_ref | string | Customer UUID |
call_start | string | When call started (from /calls/start) |
call_end | string | When call ended |
duration_seconds | number | Call duration in seconds |
intents_updated | number | Number of intents saved/updated |
variables_updated | number | Number of variables saved/updated |
GET /v1/health
Health check endpoint (no authentication required).
Request:
curl https://api.stickycalls.com/v1/health
Response:
{
"status": "healthy",
"timestamp": "2026-02-07T10:30:00.000Z",
"database": "connected"
}
Error Codes
Sticky Calls has two independent limiting systems:
- Monthly Quota (monthly budget) → 402 error when exhausted
- Rate Limits (per-minute throttle) → 429 error when exceeded
Both systems apply. See Understanding Limits for complete details.
| Code | Meaning | System | What To Do |
|---|---|---|---|
| 400 | Bad Request - Invalid parameters | - | Check request body matches schema |
| 401 | Unauthorized - Invalid or missing API key | - | Verify API key is correct and not revoked |
| 402 | Payment Required - Monthly quota exhausted | Monthly Quota | Upgrade plan or wait for monthly renewal |
| 409 | Conflict - Duplicate call_id or already ended | - | Use unique call_id for each call |
| 429 | Too Many Requests - Rate limit exceeded | Rate Limit | Wait retryAfter seconds, implement exponential backoff |
| 500 | Internal Server Error - Server error | - | Retry, contact support if persists |
Full error examples: See Error Handling Guide
Monthly Quota System (402 Payment Required)
Monthly budget - Each API call counts toward your monthly quota. When quota is exhausted, all billable endpoints return 402.
Error Response:
HTTP 402 Payment Required
{
"error": "Payment Required",
"message": "Monthly quota exhausted. Your account has 0 API calls remaining.",
"quota_remaining": 0,
"billing_account_id": "ba_1a2b3c4d5e6f..."
}
No grace period - Hard stop at 0 remaining calls.
What to do:
- Upgrade to higher tier via dashboard
- Wait for monthly renewal
Rate Limiting System (429 Too Many Requests)
Per-minute throttle - Prevents burst traffic abuse. Has 10% grace period before hard blocking.
Error Response:
HTTP 429 Too Many Requests
{
"error": "Too Many Requests",
"message": "Rate limit exceeded for your tier. Limit: 100 requests/minute.",
"retryAfter": 42,
"currentUsage": 111,
"limit": 100,
"resetAt": "2026-02-07T10:31:00.000Z"
}
Response Fields:
retryAfter: Seconds to wait before retryingcurrentUsage: Your request count in last 60 secondslimit: Your tier's base limit (not including grace period)resetAt: When the rate limit window resets (ISO timestamp)
What to do:
- Wait
retryAfterseconds - Implement exponential backoff
- Monitor
X-RateLimit-Remainingheader - Upgrade tier if consistently hitting limits
Other Error Response Formats
400 Bad Request:
{
"error": "Bad Request",
"message": "Validation failed",
"details": {
"issues": [
{
"path": ["identity_hints"],
"message": "identity_hints must include ani, external_ids, or customer_ref"
}
]
}
}
401 Unauthorized:
{
"error": "Unauthorized",
"message": "Invalid or missing API key"
}
Rate Limits
Rate limits control how fast you can make requests (separate from monthly quota).
Rate limits vary by tier with a 10% grace period before hard blocking:
| Tier | Base Limit | Grace Period (+10%) | Hard Block At |
|---|---|---|---|
| Free | 100 req/min | 110 req/min | 111+ req/min |
| Starter | 600 req/min | 660 req/min | 661+ req/min |
| Growth | 3,000 req/min | 3,300 req/min | 3,301+ req/min |
| Scale | 10,000 req/min | 11,000 req/min | 11,001+ req/min |
Grace Period Behavior:
- Requests 1-100 (Free tier): Normal operation
- Requests 101-110: Grace period - requests succeed, warning logged
- Request 111+: Hard block - 429 error returned
How it works:
- ⏱️ Rolling 60-second window (not fixed minute boundaries)
- ⚡ 10% grace period allows brief burst traffic
- 📊 Rate limit headers included in every response
- 🚫 Hard block after grace period exceeded
Rate Limit Headers:
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 523
X-RateLimit-Reset: 1675789200000
Best Practice: Monitor X-RateLimit-Remaining and implement exponential backoff for 429 responses.
Important: Rate limits are separate from monthly quota. See Understanding Limits for how both systems work together.
Monthly Quota & Billing
Monthly quota billing:
- API calls count toward your monthly quota
- Failed requests (4xx, 5xx) are not counted
- Both test and production API keys count toward your quota
Pricing Tiers: See Pricing
Idempotency
Use the Idempotency-Key header for safe retries on /calls/end:
curl -X POST https://api.stickycalls.com/v1/calls/end \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: end_call_001_attempt1" \
-H "Content-Type: application/json" \
-d '{"call_id": "call_001", ...}'
- Same idempotency key returns cached response (no duplicate save)
- Keys expire after 24 hours
- Only applicable to /calls/end
- Recommended for production use
Best Practices
✅ DO:
- Always call /calls/start at beginning of call
- Always call /calls/end at end of call
- Send
external_idswhen available (boosts confidence to 0.9+) - Use unique
call_idfor each call (timestamp + random recommended) - Use same
customer_reffor same customer across calls - Interpret confidence correctly (reuse ≥0.5, confirm 0.3-0.5, ignore <0.3)
- Save meaningful variables (name, preferences, account details)
- Mark intents
status: "open"if unresolved - Use Idempotency-Key header for /calls/end
- Implement exponential backoff for 429 errors
❌ DON'T:
- Don't skip /calls/start - you'll miss returning customer context
- Don't skip /calls/end - context won't be saved for next call
- Don't reuse
call_idwithin 30 days - violates uniqueness constraint - Don't save sensitive data (SSN, credit card numbers, passwords)
- Don't expose confidence scores to customers ("I'm 70% sure you're John")
- Don't mark intents
status: "open"if they're resolved - Don't use different
customer_reffor same customer
Code Examples
See Quick Start Guide for complete code examples in:
- Node.js / TypeScript
- Python
- Bash / curl
Need Help?
- Quick Start Guide - Get started in 5 minutes
- Best Practices - Production patterns
- AI Agent Guide - AI integration
- Contact Support - Get help
Ready to build? Sign up now →