The Sensay API provides access to conversation data and messages through specialized endpoints that use cursor-based pagination for efficient navigation through large message histories. Analytics endpoints provide aggregated insights into your replicas' usage patterns and performance metrics.
Conversation mentions
The mentions endpoint allows you to retrieve conversation messages grouped into "mention groups" (containing replica replies and context) and "placeholder groups" (representing collapsed user-only messages).
Conversation messages
The messages endpoint allows you to retrieve conversation messages. This is useful for expanding placeholders received from the mentions endpoint.
Cursor-based pagination
Conversation endpoints use cursor-based pagination with unique identifiers (UUIDs) to navigate through datasets. This approach is efficient for large datasets and handles concurrent changes gracefully.
Parameters
limit
: Number of items to fetch (1-100)beforeUUID
: Return items before this UUID (excluding the UUID itself)afterUUID
: Return items after this UUID (excluding the UUID itself)
Basic usage
Get the first page (latest mentions):
curl -X GET "https://api.sensay.io/v1/replicas/{replicaUUID}/conversations/{conversationUUID}/mentions?limit=20" \
-H "X-ORGANIZATION-SECRET: $ORGANIZATION_SECRET" \
-H "X-API-Version: $API_VERSION" \
-H "Content-Type: application/json"
Response:
{
"success": true,
"items": [
{
"type": "mention",
"messages": [
{
"uuid": "03db5651-cb61-4bdf-9ef0-89561f7c9c53",
"content": "Hello, how are you?",
"role": "user",
"createdAt": "2024-09-24T09:09:55.66709+00:00",
"source": "web",
"replicaUUID": "f0e4c2f7-ae27-4b35-89bf-7cf729a73687"
},
{
"uuid": "04ea3f1b-df72-4c98-a8f1-2ef820b5c94d",
"content": "I'm doing well, thank you for asking!",
"role": "assistant",
"createdAt": "2024-09-24T09:10:15.33421+00:00",
"source": "web",
"replicaUUID": "f0e4c2f7-ae27-4b35-89bf-7cf729a73687"
}
]
},
{
"type": "placeholder",
"count": 15
}
],
"count": 2
}
Navigation
Get the next page (older mentions):
# Use the UUID of the last message from the previous response
curl -X GET "https://api.sensay.io/v1/replicas/{replicaUUID}/conversations/{conversationUUID}/mentions?limit=20&beforeUUID=03db5651-cb61-4bdf-9ef0-89561f7c9c53" \
-H "X-ORGANIZATION-SECRET: $ORGANIZATION_SECRET" \
-H "X-API-Version: $API_VERSION" \
-H "Content-Type: application/json"
Get newer mentions:
# Use the UUID of the first message from the current page
curl -X GET "https://api.sensay.io/v1/replicas/{replicaUUID}/conversations/{conversationUUID}/mentions?limit=20&afterUUID=ec46b4db-3f0d-4392-b0f5-9fe327922e8a" \
-H "X-ORGANIZATION-SECRET: $ORGANIZATION_SECRET" \
-H "X-API-Version: $API_VERSION" \
-H "Content-Type: application/json"
Implementation pattern
async function loadNextPage(lastMessageUUID) {
const response = await fetch(
`/mentions?limit=20&beforeUUID=${lastMessageUUID}`
);
return response.json();
}
async function loadPreviousPage(firstMessageUUID) {
const response = await fetch(
`/mentions?limit=20&afterUUID=${firstMessageUUID}`
);
return response.json();
}
// Usage
const firstPage = await fetch('/mentions?limit=20');
const data = await firstPage.json();
// Get the last message UUID for next page
const lastMessage = data.items
.filter(item => item.type === 'mention')
.flatMap(mention => mention.messages)
.pop();
if (lastMessage) {
const nextPage = await loadNextPage(lastMessage.uuid);
}
Placeholder expansion
Expand a placeholder chronologically before a known message UUID:
# Use the UUID of the first message after the placeholder
curl -X GET "https://api.sensay.io/v1/replicas/{replicaUUID}/conversations/{conversationUUID}/messages?limit=20&beforeUUID=03db5651-cb61-4bdf-9ef0-89561f7c9c53" \
-H "X-ORGANIZATION-SECRET: $ORGANIZATION_SECRET" \
-H "X-API-Version: $API_VERSION" \
-H "Content-Type: application/json"
Expand a placeholder chronologically after a known message UUID:
# Use the UUID of the last message before the placeholder
curl -X GET "https://api.sensay.io/v1/replicas/{replicaUUID}/conversations/{conversationUUID}/mentions?limit=20&afterUUID=ec46b4db-3f0d-4392-b0f5-9fe327922e8a" \
-H "X-ORGANIZATION-SECRET: $ORGANIZATION_SECRET" \
-H "X-API-Version: $API_VERSION" \
-H "Content-Type: application/json"
Implementation pattern
async function expandPlaceholderBefore(firstMessageUUID) {
const response = await fetch(
`/messages?limit=20&afterUUID=${firstMessageUUID}`
);
return response.json();
}
async function expandPlaceholderAfter(lastMessageUUID) {
const response = await fetch(
`/messages?limit=20&afterUUID=${lastMessageUUID}`
);
return response.json();
}
// Usage
const mentions = await fetch('/mentions?limit=20');
const { items } = await mentions.json();
const placeholders = items.reduce((acc, item, ix, array) => {
if (item.type === 'placeholder') {
const mentionBefore = array[ix - 1]
const mentionAfter = array[ix + 1]
const firstMessageBeforePlaceholder = mentionBefore?.messages.at(-1).uuid
const firstMessageAfterPlaceholder = mentionAfter?.messages.at(-1).uuid
acc.push({
...item,
firstMessageBeforePlaceholder,
firstMessageAfterPlaceholder
})
}
return acc;
}, [])
// Expand the most recent placeholder
const messages = await fetch(`/messages?afterUUID=${placeholders.at(-1).firstMessageBeforePlaceholder}`)
Understanding message types
Mention items
Mentions represent groups of messages where an interaction with the replica occurred:
type
: Always "mention"messages
: Message items that are part of this mention
Message items
Messages represent actual conversation content with full details:
uuid
: Unique identifier for cursor navigationcontent
: The message textrole
: Either "user" or "assistant"createdAt
: Timestamp when the message was createdsource
: Where the message originated (web, telegram, discord, etc.)replicaUUID
: The replica involved in this conversation
Placeholder items
Placeholders represent collapsed groups of messages to improve navigation:
type
: Always "placeholder"count
: Number of messages represented by this placeholder
Placeholders typically represent stretches of user-only messages between assistant interactions, allowing you to focus on the most relevant parts of long conversations.
Best practices
Performance
Choose appropriate limits:
- 10-50 items for UI lists
- Up to 100 for data processing
Cache responses when possible:
- Cursor positions are stable and cache-friendly
- Message content rarely changes once created
Error handling
// Handle invalid cursors
try {
const response = await fetch(`/mentions?beforeUUID=${uuid}`);
if (response.status === 400) {
// UUID doesn't exist in this conversation
// Fall back to loading from the beginning
return fetch('/mentions?limit=20');
}
} catch (error) {
console.error('Conversation pagination error:', error);
}
UI considerations
- Loading states: Always show loading indicators during pagination requests
- Empty states: Handle conversations with no assistant interactions
- Error states: Provide retry mechanisms for failed requests
- Real-time updates: Consider how new messages affect cursor positions
Conversation analytics
The Sensay API provides analytics endpoints that offer insights into conversation patterns and usage across different communication channels for your replicas.
Conversation Analytics overview
The Conversation Analytics endpoints help you understand how your replicas are being used. They provide aggregated data about conversation volumes across conversation sources, and over time.
Historical conversation analytics
The historical analytics endpoint provides cumulative conversation count data over the last 30 days, showing how conversation volume grows over time for your replica.
Basic usage
Get historical conversation data:
curl -X GET "https://api.sensay.io/v1/replicas/{replicaUUID}/analytics/conversations/historical" \
-H "X-ORGANIZATION-SECRET: $ORGANIZATION_SECRET" \
-H "X-API-Version: $API_VERSION" \
-H "Content-Type: application/json"
Response:
{
success: true,
items: [
{ "date": "2025-05-01", "cumulativeConversations": 100 },
{ "date": "2025-05-02", "cumulativeConversations": 103 },
{ "date": "2025-05-03", "cumulativeConversations": 108 },
{ "date": "2025-05-04", "cumulativeConversations": 115 }
]
}
Understanding historical data
- Cumulative counts: Each day shows the total number of conversations created up to that date
- 30-day window: Always returns exactly 30 data points for the last 30 days
- Historical inclusion: Conversations from before the 30-day period are included in the cumulative totals
- Date format: Dates are returned in YYYY-MM-DD format in ascending chronological order
- Aggregation boundaries: All date boundaries are based on UTC midnight
Implementation pattern
async function getHistoricalData(replicaUUID) {
const response = await fetch(
`/v1/replicas/${replicaUUID}/analytics/conversations/historical`,
{
headers: {
'X-ORGANIZATION-SECRET': process.env.ORGANIZATION_SECRET,
'X-API-Version': '2025-03-25',
'Content-Type': 'application/json'
}
}
);
if (!response.ok) {
throw new Error(`Analytics request failed: ${response.status}`);
}
return response.json();
}
// Usage
const data = await getHistoricalData('f0e4c2f7-ae27-4b35-89bf-7cf729a73687');
// Calculate daily growth
const dailyGrowth = data.slice(1).map((day, index) => ({
date: day.date,
newConversations: day.cumulativeConversations - data[index].cumulativeConversations
}));
Source analytics
The source analytics endpoint shows how conversations are distributed across different communication channels as of today, helping you understand which platforms your replica is most active on.
Basic usage
Get conversation source distribution:
curl -X GET "https://api.sensay.io/v1/replicas/{replicaUUID}/analytics/conversations/sources" \
-H "X-ORGANIZATION-SECRET: $ORGANIZATION_SECRET" \
-H "X-API-Version: $API_VERSION" \
-H "Content-Type: application/json"
Response:
{
"success": true,
"items": [
{ "source": "telegram", "conversations": 245 },
{ "source": "discord", "conversations": 123 },
{ "source": "web", "conversations": 89 },
{ "source": "embed", "conversations": 34 }
]
}
Available sources
telegram
: Conversations from Telegram bot interactionsdiscord
: Conversations from Discord bot interactionsweb
: Conversations from the web interface (sensay.io)embed
: Conversations from embedded widgets on websites
Understanding source data
- Active sources only: Only communication channels with at least one conversation are included
- Total conversations: Each source shows the total number of conversations initiated from that platform
- Empty results: Replicas with no conversations return an empty array
Implementation pattern
async function getSourceAnalytics(replicaUUID) {
const response = await fetch(
`/v1/replicas/${replicaUUID}/analytics/conversations/sources`,
{
headers: {
'X-ORGANIZATION-SECRET': process.env.ORGANIZATION_SECRET,
'X-API-Version': '2025-03-25',
'Content-Type': 'application/json'
}
}
);
if (!response.ok) {
throw new Error(`Analytics request failed: ${response.status}`);
}
return response.json();
}
// Usage
const { items: sources } = await getSourceAnalytics('f0e4c2f7-ae27-4b35-89bf-7cf729a73687');
// Calculate percentages
const totalConversations = sources.reduce((sum, source) => sum + source.conversations, 0);
const sourcePercentages = sources.map(source => ({
...source,
percentage: ((source.conversations / totalConversations) * 100).toFixed(1)
}));
Analytics Best practices
Performance considerations
- Cache analytics data: Analytics queries can be resource-intensive, so cache results when possible
- Rate limiting: Be mindful of API rate limits when requesting analytics for multiple replicas
- Batch processing: When analyzing multiple replicas, consider implementing batch processing with delays
Error handling
async function safeAnalyticsRequest(endpoint, replicaUUID) {
try {
const response = await fetch(`/v1/replicas/${replicaUUID}/analytics/${endpoint}`);
if (response.status === 404) {
// Replica doesn't exist or no access
return null;
}
if (response.status === 403) {
// Insufficient permissions
throw new Error('Access denied to replica analytics');
}
if (!response.ok) {
throw new Error(`Analytics request failed: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Analytics request error:', error);
throw error;
}
}
Data visualization
Historical data works well with line charts to show conversation growth trends:
// Prepare data for chart libraries
function prepareHistoricalChart(data) {
return {
labels: data.map(d => d.date),
datasets: [{
label: 'Cumulative Conversations',
data: data.map(d => d.cumulativeConversations),
borderColor: 'rgb(75, 192, 192)',
tension: 0.1
}]
};
}
Source data works well with pie charts or bar charts:
// Prepare data for source distribution charts
function prepareSourceChart(data) {
return {
labels: data.map(d => d.source),
datasets: [{
label: 'Conversations by Source',
data: data.map(d => d.conversations),
backgroundColor: [
'rgb(255, 99, 132)',
'rgb(54, 162, 235)',
'rgb(255, 205, 86)',
'rgb(75, 192, 192)'
]
}]
};
}
Limitations
- Experimental status: These endpoints are currently in experimental phase and may change
- Limited timeframe: Historical data is limited to the last 30 days
- Aggregate data only: Analytics provide summary statistics, not individual conversation details
- Access control: Analytics are subject to the same replica access permissions as other endpoints