Skip to content

Analytics System Documentation ​

This document covers all analytics functionality across the platform both Customer Analytics (call stats, campaign metrics) and Admin Analytics (token consumption tracking). Use this as the single reference for understanding data flows, API endpoints, MongoDB collections, and frontend integration.


Table of Contents ​

  1. Architecture Overview
  2. Data Sources (MongoDB Collections)
  3. Customer Analytics
  4. Admin Analytics (Token Consumption)
  5. Feature Gating System
  6. Frontend Integration
  7. API Reference
  8. Adding New Analytics

Architecture Overview ​

┌─────────────────────────────────────────────────────────────────┐
│                        Frontend (React)                          │
├─────────────────────────────────────────────────────────────────┤
│  AnalyticsPage    │  CampaignsPage    │  HistoryPage            │
│  - Call stats     │  - Token badges   │  - Contact tokens       │
│  - Daily volume   │  - Campaign stats │  - Call metrics         │
│  - Token chart    │                   │                         │
├─────────────────────────────────────────────────────────────────┤
│  campaignService  │  tokenAnalyticsService (silentApi)          │
├─────────────────────────────────────────────────────────────────┤
│                        Backend (Express)                         │
├─────────────────────────────────────────────────────────────────┤
│  campaign.controller    │  tokenAnalytics.controller            │
│  (getDashboardStats)    │  (getCampaignTokens, getTimeline,     │
│                         │   getContactTokens)                   │
├─────────────────────────────────────────────────────────────────┤
│                     MongoDB Collections                          │
│  Contact  │  Campaign  │  ContactObservability  │  CreditTxn    │
└─────────────────────────────────────────────────────────────────┘

Data Sources ​

Contact Collection ​

Primary collection for all call/contact data. Key fields for analytics:

FieldTypeDescription
contactUIDstringUnique contact identifier
campaignUIDstringParent campaign
orgUIDstringOrganization (tenant isolation)
userIdstringOwner user
AgentIDstringAgent used for the call
statusenumpending, in_progress, done, failed, skipped, stopped
callStatusstringGranular status: ended, not_picked, rejected, media_failure, busy, etc.
callDurationnumberActual call duration (seconds)
billingDurationnumberBilled duration (seconds)
callDirectionstringinbound or outbound
analysisobjectCall analysis results (schema-dependent)
createdAtDateContact creation time
updatedAtDateLast update time
isTestCallbooleanExcluded from analytics

ContactObservability Collection ​

Stores AI model usage metrics per contact. Linked to Contact via contactUID.

typescript
{
  contactUID: string;          // Links to Contact.contactUID
  observability: {
    Metrics: {
      model_usage: [           // Array of usage entries per AI call
        {
          type: string;        // "llm_usage" | "tts_usage" | "stt_usage"
          provider: string;    // e.g. "openai", "elevenlabs", "deepgram"
          model: string;       // e.g. "gpt-4o", "eleven_multilingual_v2"
          input_tokens: number;       // LLM only
          output_tokens: number;      // LLM only
          input_cached_tokens: number; // LLM only
          characters_count: number;    // TTS only
          audio_duration: number;      // TTS and STT (seconds)
        }
      ]
    }
  };
  session_report: object;      // Full session report (not used in analytics)
}

Campaign Collection ​

Campaign metadata. Key fields:

FieldTypeDescription
campaignUIDstringUnique campaign identifier
orgUIDstringOrganization
userIdstringOwner
AgentIDstringDefault agent
isRunningbooleanCurrently active
stopRequestedbooleanStop in progress

CreditTransaction Collection ​

Used for inbound call credit tracking in dashboard stats.


Customer Analytics ​

What It Shows ​

Available to all users with voice_campaign feature. No additional feature gate required.

Analytics Page (/analytics):

  • Overview cards: total campaigns, active campaigns, total agents, total contacts, answer rate, avg call duration, total billing duration, credits consumed/balance
  • Daily call volume chart (area or bar chart)
  • Call status breakdown (pie chart with granular statuses)
  • Inbound metrics: call count, duration, credits consumed
  • Success rate per campaign (when campaign filter is active)
  • Analysis schema filters (filter by call analysis results)

History Page (/history):

  • Campaign-level stats: total contacts, answered, not answered, in progress, pending, stopped
  • Per-contact details: call status, duration, recording, transcription, analysis
  • Filterable by campaign, agent, status, direction, date range

Campaigns Page (/campaigns):

  • Per-campaign stats in table: total contacts, done, failed, pending counts

Backend Endpoint ​

GET /dashboard-stats

Query Parameters:

ParamRequiredDescription
rangeNotoday, yesterday, 7d, 30d, custom (default: 7d)
fromIf customStart date YYYY-MM-DD
toIf customEnd date YYYY-MM-DD
campaignUIDNoFilter to specific campaign
agentIdNoFilter by agent
directionNoinbound or outbound
analysis.*NoDot-notation analysis filters

Response shape:

json
{
  "success": true,
  "dashboard": {
    "range": "custom",
    "dateFrom": "ISO string",
    "dateTo": "ISO string",
    "overview": {
      "totalCampaigns": 10,
      "activeCampaigns": 2,
      "totalAgents": 5,
      "totalContacts": 1500,
      "contactsCalled": 1200,
      "contactsDone": 900,
      "contactsFailed": 300,
      "answerRate": 75,
      "avgCallDurationSec": 45,
      "totalBillingDurationSec": 54000,
      "creditsConsumed": 250,
      "creditBalance": 750
    },
    "dailyCallVolume": [
      { "date": "2025-01-01", "day": "Wed", "total": 50, "answered": 40, "failed": 10 }
    ],
    "callStatusBreakdown": {
      "ended": 500,
      "not_picked": 200,
      "rejected": 50
    },
    "inbound": {
      "inboundCalls": 100,
      "inboundDuration": 3600,
      "inboundCredits": 50
    }
  }
}

Data Flow ​

  1. Frontend campaignService.getDashboardStats(params) calls the endpoint
  2. Backend builds MongoDB aggregation pipeline on Contact collection
  3. Filters by: user's campaigns, date range, agent, direction, analysis
  4. Aggregates: counts by status, sums durations, computes rates
  5. Joins CreditTransaction for inbound credit tracking
  6. Returns structured dashboard object

Admin Analytics (Token Consumption) ​

What It Shows ​

Available only when admin_analytics feature is enabled. Shows AI model usage (LLM tokens, TTS characters, STT audio duration) aggregated from the ContactObservability collection.

Campaigns Page Token Usage Column:

  • Compact inline badge per campaign row showing: LLM total tokens, TTS characters, STT duration
  • Non-blocking fetch (doesn't delay row rendering)

Campaign Details Modal Token Consumption Section:

  • Detailed breakdown by type (LLM, TTS, STT)
  • Per provider/model: input tokens, output tokens, cached tokens (LLM); characters, audio duration (TTS); audio duration (STT)

History Page Contact Token Details:

  • Same detailed breakdown but for a single contact
  • Shown in the contact side panel's Details tab

Analytics Page Token Consumption Over Time Chart:

  • Area chart showing token usage over time
  • Filters: granularity (daily/weekly/monthly), date range (max 365 days), agent, campaign
  • Default: last 30 days, daily granularity

Backend Service ​

File: backend/client_backend/src/services/tokenAnalytics.service.ts

Three main methods:

getCampaignTokenUsage(campaignUID, orgUID) ​

Aggregation pipeline:

  1. $match contacts by campaignUID + orgUID
  2. $lookup ContactObservability via contactUID
  3. $unwind observability (preserveNullAndEmptyArrays: true)
  4. $unwind model_usage array (preserveNullAndEmptyArrays: true)
  5. $group by {type, provider, model}, summing all metrics

Returns: CampaignTokenResponse with usage breakdown + computed totals.

getTimelineTokenUsage({ orgUID, granularity, from, to, agentId?, campaignUID? }) ​

Same pipeline pattern but adds:

  • Date range filter on createdAt
  • Optional AgentID and campaignUID filters (AND logic)
  • Groups by time bucket using $dateToString with format based on granularity:
    • daily: %Y-%m-%d
    • weekly: %Y-W%V
    • monthly: %Y-%m

Returns: TimelineTokenResponse with sorted buckets.

getContactTokenUsage(contactUID, orgUID) ​

Same pipeline but matches a single contact. Verifies contact belongs to orgUID.

Returns: ContactTokenResponse with usage breakdown (no totals).

All pipelines use maxTimeMS: 5000 to prevent long-running queries. ​

Backend Routes ​

File: backend/client_backend/src/routes/tokenAnalytics.routes.ts

All routes use middleware chain: verifyJwt → requireFeatureAccess("admin_analytics")

GET /api/v1/token-analytics/campaign/:campaignUID
GET /api/v1/token-analytics/timeline?granularity=&from=&to=&agentId=&campaignUID=
GET /api/v1/token-analytics/contact/:contactUID

Controller Validation ​

File: backend/client_backend/src/controllers/tokenAnalytics.controller.ts

  • Campaign endpoint: returns 404 if campaign not found in org
  • Timeline endpoint: validates granularity (must be daily/weekly/monthly), date format, start < end, range ≤ 365 days
  • Contact endpoint: returns 404 if contact not found in org
  • All endpoints: return 403 if no orgUID on request

Feature Gating System ​

Backend (Two-Layer) ​

Middleware: backend/client_backend/src/middleware/feature.middleware.ts

requireFeatureAccess("admin_analytics")
RoleLayer 1 (Org Plan)Layer 2 (User Features)
SuperAdminBypassedBypassed
AdminCheckedBypassed
CustomerCheckedChecked
  • If org plan doesn't include the feature → 403
  • If customer doesn't have the feature assigned → 403
  • If feature service errors → 403 (deny by default)

Frontend ​

Hook: app/src/hooks/useFeature.ts

typescript
const { hasFeature } = useFeature();
const showTokens = hasFeature(FEATURES.ADMIN_ANALYTICS);

Logic mirrors backend:

  • SuperAdmin: always true
  • Admin: checks organization.plan.features
  • Customer: checks BOTH organization.plan.features AND user.features

Feature Registration ​

Backend: backend/client_backend/src/types/index.ts

  • AVAILABLE_FEATURES array includes "admin_analytics"
  • FEATURE_MAP.tokenAnalytics maps to ["admin_analytics"]

Frontend: app/src/lib/features.ts

  • FEATURES.ADMIN_ANALYTICS = 'admin_analytics'
  • UI_FEATURE_MAP.adminAnalytics = [FEATURES.ADMIN_ANALYTICS]
  • ADMIN_FEATURES = [FEATURES.ADMIN_ANALYTICS] (displayed separately in feature assignment UI)

Frontend Integration ​

Token Analytics Service ​

File: app/src/services/tokenAnalytics.service.ts

Uses a dedicated axios instance (silentApi) separate from the global api to:

  • Avoid triggering auto-toast on 403/404 errors
  • Avoid triggering 401 token refresh redirect
  • Silently return null on any failure
typescript
import { tokenAnalyticsService } from '@/services/tokenAnalytics.service';
const data = await tokenAnalyticsService.getCampaignTokenUsage(campaignUID);
// Returns CampaignTokenResponse | null

useTokenAnalytics Hook ​

File: app/src/hooks/useTokenAnalytics.ts

Provides:

  • fetchCampaignTokens(campaignUID) with in-memory cache
  • fetchTimelineTokens(params) with cache keyed by filter params
  • fetchContactTokens(contactUID) with in-memory cache
  • State objects: campaign, timeline, contact each with { data, loading, error }

UI Components ​

ComponentLocationPurpose
TokenSummaryBadgeapp/src/components/shared/Compact inline display (LLM/TTS/STT totals)
TokenDetailsSectionapp/src/components/shared/Detailed breakdown card by type/provider/model
TokenAnalyticsChartapp/src/components/shared/Timeline area chart with filters
CampaignTokenCellapp/src/components/campaigns/Non-blocking cell for campaign table rows

Integration Pattern ​

All token analytics UI is conditionally rendered:

tsx
const { hasFeature } = useFeature();
const showTokens = hasFeature(FEATURES.ADMIN_ANALYTICS);

// Only render when feature is enabled
{showTokens && <TokenDetailsSection usage={...} />}

API Reference ​

Customer Analytics ​

MethodEndpointAuthFeature GateDescription
GET/dashboard-statsJWTvoice_campaignGlobal dashboard metrics
GET/campaign-detail/:uidJWTvoice_campaignCampaign contacts + stats
GET/contactsJWTvoice_campaignPaginated contact list
GET/success-rate/:campaignUIDJWTvoice_campaignCampaign success rate

Admin Analytics (Token Consumption) ​

MethodEndpointAuthFeature GateDescription
GET/api/v1/token-analytics/campaign/:campaignUIDJWTadmin_analyticsCampaign token usage
GET/api/v1/token-analytics/timelineJWTadmin_analyticsTime-bucketed token usage
GET/api/v1/token-analytics/contact/:contactUIDJWTadmin_analyticsContact token usage

Timeline Query Parameters ​

ParamRequiredValuesDescription
granularityYesdaily, weekly, monthlyTime bucket size
fromYesYYYY-MM-DDStart date
toYesYYYY-MM-DDEnd date
agentIdNostringFilter by agent
campaignUIDNostringFilter by campaign

Response Types ​

typescript
// Campaign token response
interface CampaignTokenResponse {
  campaignUID: string;
  usage: TokenUsageBreakdown;
  totals: TokenUsageTotals;
}

// Timeline response
interface TimelineTokenResponse {
  granularity: "daily" | "weekly" | "monthly";
  from: string;
  to: string;
  buckets: TimelineBucket[];
}

// Contact token response
interface ContactTokenResponse {
  contactUID: string;
  usage: TokenUsageBreakdown;
}

// Shared types
interface TokenUsageBreakdown {
  llm: LLMUsageEntry[];
  tts: TTSUsageEntry[];
  stt: STTUsageEntry[];
}

interface TokenUsageTotals {
  llm_input_tokens: number;
  llm_output_tokens: number;
  llm_cached_tokens: number;
  tts_characters: number;
  tts_audio_duration: number;
  stt_audio_duration: number;
}

Adding New Analytics ​

Adding a New Metric to Customer Analytics ​

  1. Add the aggregation logic in campaign.controller.ts → getDashboardStats
  2. Include the new field in the response dashboard object
  3. Add the corresponding type in app/src/types/campaign.ts
  4. Display it in AnalyticsPage.tsx (stat card, chart, or table)

Adding a New Token Analytics Dimension ​

  1. Update the aggregation pipeline in tokenAnalytics.service.ts
  2. Add new fields to the types in backend/client_backend/src/types/tokenAnalytics.ts
  3. Mirror the types in app/src/types/tokenAnalytics.ts
  4. Update the UI components (TokenDetailsSection, TokenSummaryBadge, TokenAnalyticsChart)

Adding a New Feature-Gated Analytics Section ​

  1. Register the feature in backend/client_backend/src/types/index.ts (AVAILABLE_FEATURES + FEATURE_MAP)
  2. Register in app/src/lib/features.ts (FEATURES + UI_FEATURE_MAP)
  3. Create backend service + controller + routes with requireFeatureAccess("your_feature")
  4. Create frontend service (use dedicated axios instance for silent errors)
  5. Create hook + UI components
  6. Gate rendering with useFeature().hasFeature(FEATURES.YOUR_FEATURE)

Key Principles ​

  • Tenant isolation: Always scope queries by orgUID
  • Silent failures: Token analytics uses silentApi never shows error toasts or triggers auth redirects
  • Non-blocking: Campaign row token fetches don't block row rendering
  • Feature deny-by-default: If feature service errors, deny access (403)
  • Aggregation timeout: All MongoDB aggregations use maxTimeMS: 5000
  • Frontend timeout: All API calls use 10-second timeout
  • Caching: useTokenAnalytics hook caches responses in useRef Maps

Last updated: