In-depth: Conversations

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 navigation
  • content: The message text
  • role: Either "user" or "assistant"
  • createdAt: Timestamp when the message was created
  • source: 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

  1. Choose appropriate limits:

    • 10-50 items for UI lists
    • Up to 100 for data processing
  2. 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

  1. Loading states: Always show loading indicators during pagination requests
  2. Empty states: Handle conversations with no assistant interactions
  3. Error states: Provide retry mechanisms for failed requests
  4. 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 interactions
  • discord: Conversations from Discord bot interactions
  • web: 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
  1. Cache analytics data: Analytics queries can be resource-intensive, so cache results when possible
  2. Rate limiting: Be mindful of API rate limits when requesting analytics for multiple replicas
  3. 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