npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ctchealth/plato-sdk

v0.0.14

Published

A TypeScript SDK for interacting with the Plato API to create and manage AI-powered medical training simulations.

Readme

Plato SDK

A TypeScript SDK for interacting with the Plato API to create and manage AI-powered medical training simulations.

Overview

The Plato SDK provides a simple and type-safe way to integrate with the Plato platform, allowing you to create medical training simulations with AI personas and manage voice-based interactions.

Authentication

All API requests require authentication using a Bearer token. Include your JWT token in the API-AUTH header:

API-AUTH: Bearer <your-jwt-token>

Requests without a valid JWT token will be rejected with a 401 Unauthorized response.

Features

  • Create AI personas with customizable professional profiles and personalities
  • Real-time voice interactions with AI assistants
  • Comprehensive event system for call management
  • Type-safe API with full TypeScript support
  • Medical training simulation configuration
  • Simulation persistence and recovery across page reloads

Installation

npm install plato-sdk

Quick Start

import { PlatoApiClient, AvatarLanguage } from 'plato-sdk';

// Initialize the client
const client = new PlatoApiClient({
  baseUrl: 'https://your-plato-api.com',
  token: 'your-api-token',
  user: '[email protected]',
});

// Get available assistant images
const images = await client.getAssistantImages();

// Create a simulation
const simulation = await client.createSimulation({
  persona: {
    professionalProfile: {
      location: 'City Hospital, New York',
      practiceSettings: 'Outpatient Clinic',
      yearOfExperience: 12,
      specialityAndDepartment: 'Cardiology',
    },
    segment: SegmentType.CostConsciousPrescriber,
    assistantGender: AssistantVoiceGender.Male,
    name: 'Dr Vegapunk',
    personalityAndBehaviour: {
      riskTolerance: 40,
      researchOrientation: 70,
      recognitionNeed: 60,
      brandLoyalty: 55,
      patientEmpathy: 80,
    },
    context: {
      subSpecialityOrTherapyFocus: 'Hypertension management',
      typicalPatientMix: 'Elderly with comorbidities',
      keyClinicalDrivers: 'Reducing cardiovascular risk',
    },
  },
  product: {
    name: 'Ibuprofen 1000mg',
    description:
      'A nonsteroidal anti-inflammatory drug used to reduce pain, inflammation, and fever',
  },
  presentation: 'Oral tablets, 10 tablets per pack',
  scenario: 'Discussing treatment options for an elderly patient with chronic arthritis',
  objectives:
    'Demonstrate efficacy of Ibuprofen in pain management Highlight safety profile and contraindications Encourage patient adherence',
  anticipatedObjections:
    'Concerns about gastrointestinal side effects Preference for lower-cost generic alternatives Potential interactions with other medications',
  imageId: images[0]._id,
  avatarLanguage: AvatarLanguage.English,
});

// Check the simulation status - If simulation creation phase is equals to FINISHED, the simulation is ready to use
// If the simulation creation phase is ERROR the simulation creation failed and you need to retry creating a new one
// tip: you can also check the simulation status periodically using a polling mechanism
const status = await client.getSimulationStatus(simulation.simulationId);

// Start a voice call
const call = await client.startCall(simulation.simulationId);

// Listen to call events
call.on('call-start', () => {
  console.log('Call started');
});

call.on('message', message => {
  console.log('Message received:', message.transcript);
});

call.on('call-end', () => {
  console.log('Call ended - processing feedback...');
});

// Automatically receive post-call feedback when ready
call.on('call-details-ready', callDetails => {
  console.log('Call Summary:', callDetails.summary);
  console.log('Score:', callDetails.score);
  console.log('Strengths:', callDetails.strengths);
  console.log('Areas to improve:', callDetails.weaknesses);
});

// Stop the call when done
call.stopCall();

API Reference

PlatoApiClient

The main class for interacting with the Plato API.

Constructor

new PlatoApiClient(config: ApiClientConfig)

Parameters:

  • config.baseUrl (string): The base URL of the Plato API
  • config.token (string): Your API authentication token
  • config.user (string): Your user identifier
  • config.jwtToken (string, optional): A per-user JWT token sent as the x-client-token header on every request

Example with JWT token:

const client = new PlatoApiClient({
  baseUrl: 'https://your-plato-api.com',
  token: 'your-api-key',
  user: '[email protected]',
  jwtToken: 'eyJhbGciOiJSUzI1NiIs...', // optional, per-user token
});

Methods

setJwtToken(jwtToken: string)

Updates the JWT token for all subsequent requests. Use this when the token is refreshed or when switching user context.

client.setJwtToken('new-jwt-token');
clearJwtToken()

Removes the JWT token from subsequent requests.

client.clearJwtToken();
createSimulation(params: CreateSimulationDto)

Creates a new medical training simulation. It may take a few minutes for the simulation to be ready for use.

Returns: Promise<{ simulationId: string, phase: CreationPhase }>

startCall(simulationId: string)

Starts a voice call with the AI persona for the specified simulation.

Returns: Object with:

  • stopCall(): Function to end the call
  • callId: Unique identifier for the call
  • on<K>(event: K, listener: CallEventListener<K>): Subscribe to call events
  • off<K>(event: K, listener: CallEventListener<K>): Unsubscribe from call events
getCallDetails(callId: string)

Retrieves detailed information about a completed call, including transcript, summary, recording URL, ratings, and evaluation metrics.

Parameters:

  • callId (string): The MongoDB _id of the call

Returns: Promise<CallDTO> — An object containing:

  • _id: MongoDB ID of the call
  • summary: Summary of the conversation
  • transcript: Full transcript of the call
  • recordingUrl: URL to access the call recording
  • rating: User-provided rating (0-5)
  • successEvaluation: Boolean indicating if the call was successful
  • score: Overall score for the call
  • strengths: Array of identified strengths
  • weaknesses: Array of identified weaknesses
  • metric1, metric2, metric3: Evaluation metric names
  • metric1Value, metric2Value, metric3Value: Values for each metric
  • createdAt: Timestamp when the call was created
  • endedAt: Timestamp when the call ended
  • callDurationMs: Duration of the call in milliseconds
  • And other call-related fields

Example:

// After a call has ended, retrieve its details
const callDetails = await client.getCallDetails(call._id);
console.log('Call Summary:', callDetails.summary);
getCallRecordings(queryParams: SimulationRecordingsQueryDto)

Retrieves a paginated list of call recordings for the authenticated user.

Parameters:

  • queryParams (SimulationRecordingsQueryDto): Query parameters for filtering and pagination
    • limit (optional): Number of recordings per page (5, 10, or 25)
    • page (optional): Page number for pagination
    • sort (optional): Sort order ('asc' or 'desc')

Returns: Promise<SimulationRecordingsDto[]> — An array of recording objects, each containing:

  • _id: MongoDB ID of the call
  • createdAt: Timestamp when the call was created
  • recordingStatus: Status of the recording ('STARTED', 'PROCESSING', 'FINISHED', or 'FAILED')

Example:

// Get the 10 most recent call recordings
const recordings = await client.getCallRecordings({
  limit: 10,
  page: 1,
  sort: 'desc',
});

recordings.forEach(recording => {
  console.log(`Call ${recording._id} - Status: ${recording.recordingStatus}`);
});
getCallRecording(callId: string)

Retrieves the recording URL for a specific call.

Parameters:

  • callId (string): The MongoDB _id of the call

Returns: Promise<string> — The URL to access the call recording

Example:

// Get the recording URL for a specific call
const recordingUrl = await client.getCallRecording(call._id);
console.log('Recording URL:', recordingUrl);
uploadPdfSlides(file: File | Blob)

Uploads a PDF file for slide analysis. The file will be uploaded to S3 and analyzed asynchronously.

Parameters:

  • file (File | Blob): The PDF file to upload

Returns: Promise<string> — A success message if the upload was successful.

Example:

// Upload a PDF file from an input element
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
const file = fileInput.files?.[0];

if (file) {
  const result = await client.uploadPdfSlides(file);
  console.log('PDF upload initiated');
}
getSlidesAnalysis(queryParams: PdfSlidesAnalysisQueryDto)

Retrieves a paginated and sorted list of PDF slide analysis records.

Parameters:

  • queryParams (PdfSlidesAnalysisQueryDto):
    • limit (optional): Number of records per page
    • page (optional): Page number
    • sort (optional): Sort order ('asc' or 'desc')
    • sortBy (optional): Field to sort by ('createdAt', 'originalFilename', or 'totalSlides')

Returns: Promise<PdfSlidesDto[]>

Example:

const analysisList = await client.getSlidesAnalysis({
  limit: 10,
  page: 0,
  sort: 'desc',
  sortBy: 'createdAt',
});
getSlideAnalysis(id: string)

Retrieves the detailed analysis data for a specific PDF slide record, including the overview and individual slide analysis.

Parameters:

  • id (string): The unique ID of the PDF slide record

Returns: Promise<PdfSlideDto>

Example:

const details = await client.getSlideAnalysis('pdf-slide-id');
console.log('Lesson Overview:', details.lessonOverview);
deleteSlideAnalysis(id: string)

Deletes a PDF slide analysis record from the database and removes the corresponding file from S3 storage.

Parameters:

  • id (string): The unique ID of the PDF slide record to delete

Returns: Promise<void>

Example:

await client.deleteSlideAnalysis('pdf-slide-id');
console.log('Analysis deleted successfully');
getAssistantImages()

Retrieves all available assistant images that can be used when creating a simulation.

Returns: Promise<AssistantImageDto[]> — An array of assistant image objects, each containing:

  • _id: Unique identifier for the image
  • imageUrl: URL of the assistant image

Example:

// Get all available assistant images
const images = await client.getAssistantImages();
getRecommendations()

Retrieves recommendations based on the user's recent call performance. Analyzes the 5 most recent calls using a sliding window of 3 calls to identify patterns and provide actionable insights. Calls shorter than 2 minutes are excluded from analysis. No new recommendations are generated if fewer than 3 eligible calls are available.

Returns: Promise<RecommendationsResponseDto> — An object containing:

  • recommendations: Array of recommendation objects, each with:
    • title: Brief title of the recommendation
    • description: Detailed, actionable description
    • priority: Priority level ('high', 'medium', or 'low')
  • strengths: Array of identified strengths, each with:
    • strength: Description of the strength
    • frequency: Number of calls where this strength appeared
  • improvementAreas: Array of areas needing improvement, each with:
    • area: Description of the improvement area
    • frequency: Number of calls where this weakness appeared
  • summary: A 2-3 sentence overall summary of performance trends
  • callsAnalyzed: Number of calls that were analyzed
  • generatedAt: Timestamp when the recommendations were generated

Example:

// Get personalized recommendations based on call history
const recommendations = await client.getRecommendations();

console.log(`Analyzed ${recommendations.callsAnalyzed} calls`);
console.log('Summary:', recommendations.summary);

// Display high-priority recommendations
recommendations.recommendations
  .filter(rec => rec.priority === 'high')
  .forEach(rec => {
    console.log(`[HIGH] ${rec.title}: ${rec.description}`);
  });

// Show recurring strengths
recommendations.strengths.forEach(s => {
  console.log(`Strength (${s.frequency} calls): ${s.strength}`);
});

// Show areas for improvement
recommendations.improvementAreas.forEach(area => {
  console.log(`Needs work (${area.frequency} calls): ${area.area}`);
});

Note: This method requires the user to have at least one completed call. If no calls are found, a BadRequestException will be thrown.

Checking Simulation Status

You can check the current phase/status of a simulation using the checkSimulationStatus method. This is useful for polling the simulation creation process until it is ready or has failed.

const status = await client.checkSimulationStatus(simulation.simulationId);
console.log('Current phase:', status.phase); // e.g., 'FINISHED', 'ERROR', etc.
  • Returns: { phase: CreationPhase } — The current phase of the simulation creation process.
  • Typical usage: Poll this method until the phase is FINISHED or ERROR before starting a call.

Simulation Persistence

Simulations are persisted server-side and can be recovered after page reloads. Store the simulation ID client-side (e.g., localStorage) for quick recovery.

getSimulationDetails(simulationId: string)

Retrieves detailed information about a simulation, including its original configuration.

Returns: Promise<SimulationDetailsDto> containing:

  • simulationId: MongoDB ID
  • phase: Current creation phase
  • assistantId: The assistant ID (available after creation)
  • configuration: Original CreateSimulationDto
  • createdAt, updatedAt: Timestamps

Example:

// Store simulation ID after creation
const simulation = await client.createSimulation(config);
localStorage.setItem('plato_simulation_id', simulation.simulationId);

// Recover simulation on page reload
const simulationId = localStorage.getItem('plato_simulation_id');
if (simulationId) {
  const details = await client.getSimulationDetails(simulationId);

  if (details.phase !== CreationPhase.ERROR) {
    // Resume polling if still in progress
    if (details.phase !== CreationPhase.FINISHED) {
      startPolling(details.simulationId);
    }
  } else {
    localStorage.removeItem('plato_simulation_id');
  }
}

getUserSimulations()

Retrieves all simulations for the authenticated user. Useful when the simulation ID is not stored locally.

Returns: Promise<Array<{ simulationId: string; phase: CreationPhase }>>

Example:

const simulations = await client.getUserSimulations();
const inProgress = simulations.find(
  sim => sim.phase !== CreationPhase.FINISHED && sim.phase !== CreationPhase.ERROR
);

Call Recovery

The SDK automatically handles call recovery in case of page refreshes or browser closures during active calls. This ensures that call data is never lost and all calls get properly processed by the backend, even if the page is refreshed while a call is in progress.

How It Works

When you start a call, the SDK stores minimal call state in the browser's localStorage. If the page is refreshed:

  1. On app initialization: The recoverAbandonedCall() method checks for any stored call state
  2. Age-based recovery: Calls older than 5 minutes are considered abandoned and automatically recovered
  3. Backend notification: The backend is notified to process the call's transcript, recording, and analytics
  4. Automatic cleanup: The stored state is cleared after recovery or when a call ends naturally

This recovery mechanism works in two stages to ensure complete data integrity:

  • Stage 1 (App Init): Recovers truly abandoned calls (>5 minutes old)
  • Stage 2 (New Call Start): Notifies backend of ANY previous call before starting a new one

This dual approach ensures no call data is ever lost, regardless of user behavior patterns.

Integration

Angular

Call recoverAbandonedCall() during component initialization:

import { Component, OnInit } from '@angular/core';
import { PlatoApiClient } from 'plato-sdk';

@Component({
  selector: 'app-training',
  templateUrl: './training.component.html',
})
export class TrainingComponent implements OnInit {
  constructor(private platoClient: PlatoApiClient) {}

  async ngOnInit(): Promise<void> {
    // Recover any abandoned calls from previous session
    const recovered = await this.platoClient.recoverAbandonedCall();

    if (recovered) {
      console.log('Recovered abandoned call from previous session');
      // Optional: Show notification to user
      this.showNotification('Previous call data recovered and processed');
    }

    // Continue with normal initialization
    await this.loadSimulations();
  }

  showNotification(message: string) {
    // Your notification logic here
  }
}

React

Call recoverAbandonedCall() in a useEffect hook:

import { useEffect, useState } from 'react';
import { PlatoApiClient } from 'plato-sdk';

function TrainingApp() {
  const [platoClient] = useState(() => new PlatoApiClient({
    baseUrl: 'https://your-api.com',
    token: 'your-token',
    user: 'your-user',
  }));

  useEffect(() => {
    const recoverCall = async () => {
      try {
        const recovered = await platoClient.recoverAbandonedCall();

        if (recovered) {
          console.log('Recovered abandoned call');
          // Optional: Show toast notification
          showToast('Previous call data recovered');
        }
      } catch (error) {
        console.error('Recovery error:', error);
      }
    };

    recoverCall();
  }, [platoClient]);

  return (
    // Your app UI
  );
}

recoverAbandonedCall()

Recovers and processes any abandoned calls from previous sessions.

Returns: Promise<boolean>

  • true if an abandoned call was found and recovered
  • false if no abandoned call exists or the call is too recent (<5 minutes)

Example:

// Check if any calls were recovered
const wasRecovered = await client.recoverAbandonedCall();

if (wasRecovered) {
  // An abandoned call was processed
  console.log('Successfully recovered abandoned call');
} else {
  // No recovery needed
  console.log('No abandoned calls to recover');
}

Behavior Details

When Recovery Happens

Scenario 1: Abandoned Call (>5 minutes)

10:00:00 - User starts call → State stored
10:02:00 - User closes browser
10:08:00 - User returns and opens app
10:08:01 - recoverAbandonedCall() detects call (>5 min old)
10:08:01 - Backend notified → Call processed ✓

Scenario 2: Recent Call (<5 minutes)

10:00:00 - User starts call → State stored
10:01:00 - User refreshes page
10:01:01 - recoverAbandonedCall() checks call (1 min old)
10:01:01 - Too recent, skip recovery
10:03:00 - User starts new call
10:03:00 - startCall() detects previous call
10:03:00 - Backend notified of previous call → Processed ✓
10:03:01 - New call starts

Automatic Cleanup

The SDK automatically clears stored call state when:

  1. Call ends naturally: When call-end event fires
  2. User stops call: When stopCall() is called manually
  3. New call starts: Before starting a new call (after notifying backend of previous call)
  4. After recovery: After successfully notifying backend of abandoned call

Technical Details

What Gets Stored

The SDK stores minimal state in localStorage under the key plato_active_call:

{
  callId: "mongodb-call-id",           // MongoDB ID for backend notification
  externalCallId: "external-provider-id", // External provider's call ID
  simulationId: "simulation-id",        // Associated simulation
  startedAt: "2025-01-15T10:00:00Z",   // ISO 8601 timestamp
  version: 1                            // Schema version for future compatibility
}

Privacy & Storage

  • Storage location: Browser localStorage (persists across sessions)
  • Data size: ~300 bytes (minimal footprint)
  • Privacy: Stored locally, never sent to third parties
  • Graceful degradation: If localStorage is disabled (e.g., private browsing), the SDK continues to work normally but recovery won't be available

Backend Idempotency

The backend /api/v1/postcall/call-ended endpoint is idempotent, meaning:

  • ✅ Safe to call multiple times for the same call
  • ✅ No duplicate processing or data corruption
  • ✅ Recovery logic can safely notify backend even if uncertain

This design ensures data integrity over potential duplicate notifications.

Edge Cases

Multiple Tabs

If you have multiple tabs open:

  • Only the most recent call state is stored (single localStorage key)
  • Each new call in any tab overwrites previous state
  • The most recent call is recovered if needed

Impact: Minimal - calls typically happen one at a time, and the backend handles all notifications properly.

Rapid Actions

If the user refreshes immediately after starting a call:

  • The call state is stored but not considered abandoned (<5 minutes)
  • When the user starts a new call later, the previous call is automatically notified to backend
  • No data loss occurs

Browser Closure

If the browser is force-closed during a call:

  • State persists in localStorage
  • On next app launch, recovery detects and processes the call
  • Call data is recovered successfully

localStorage Disabled

If localStorage is disabled (e.g., private browsing mode):

  • All storage operations fail silently
  • No errors thrown
  • SDK continues to work for normal call flow
  • Recovery simply won't be available

This is acceptable because localStorage is enabled in 99%+ of browsers.

Best Practices

  1. Always call recoverAbandonedCall() during app initialization to ensure data integrity
  2. Call it early in your initialization flow, before other operations
  3. Handle the return value to show user notifications when calls are recovered
  4. Don't block UI - recovery is fast (<500ms typically) but async
  5. Trust the system - the SDK and backend handle all edge cases automatically

Example: Complete Integration

// Angular Component
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
})
export class AppComponent implements OnInit {
  constructor(private platoClient: PlatoApiClient, private toastr: ToastrService) {}

  async ngOnInit(): Promise<void> {
    try {
      // Step 1: Recover any abandoned calls
      const recovered = await this.platoClient.recoverAbandonedCall();

      if (recovered) {
        this.toastr.info('Previous call data was recovered and processed');
      }

      // Step 2: Continue with normal app initialization
      await this.loadUserData();
      await this.loadSimulations();
    } catch (error) {
      console.error('Initialization error:', error);
    }
  }

  async startCall(simulationId: string): Promise<void> {
    try {
      // The SDK automatically handles any previous call state
      const call = await this.platoClient.startCall(simulationId);

      call.on('call-end', () => {
        console.log('Call ended');
        // State automatically cleared
      });

      call.on('call-details-ready', callDetails => {
        console.log('Feedback ready:', callDetails);
      });
    } catch (error) {
      console.error('Call error:', error);
    }
  }
}

Troubleshooting

Q: What if I forget to call recoverAbandonedCall()?

A: The SDK still protects against data loss through Stage 2 recovery - when you start a new call, it automatically notifies the backend of any previous call. However, it's still recommended to call recoverAbandonedCall() for optimal behavior.

Q: Can I disable call recovery?

A: While you can skip calling recoverAbandonedCall(), the Stage 2 recovery (in startCall()) always runs to prevent data loss. This is by design to ensure data integrity.

Q: How do I know if a call was recovered?

A: The recoverAbandonedCall() method returns true when a call is recovered. Use this to show user notifications or update UI accordingly.

Q: What if the backend is down during recovery?

A: The recovery attempt is logged and the stored state is cleared to prevent infinite retries. The SDK continues working normally. Recovery failures are graceful and don't break the app.

Event System

The SDK provides a comprehensive event system for managing voice calls:

Available Events

  • call-start: Triggered when a call begins
  • call-end: Triggered when a call ends
  • speech-start: Triggered when speech detection starts
  • speech-end: Triggered when speech detection ends
  • message: Triggered when a message is received (contains transcript and metadata)
  • volume-level: Triggered with volume level updates (number)
  • error: Triggered when an error occurs
  • call-details-ready: Triggered when post-call processing completes and call details are available (includes full CallDTO with transcript, summary, ratings, and evaluation)

Event Usage

// Subscribe to events
call.on('message', message => {
  console.log('Transcript:', message.transcript);
  console.log('Type:', message.transcriptType); // 'final' or 'partial'
});

call.on('volume-level', level => {
  console.log('Volume level:', level);
});

call.on('error', error => {
  console.error('Call error:', error);
});

// Listen for call details after post-call processing
call.on('call-details-ready', callDetails => {
  console.log('Post-call feedback ready:', callDetails);
});

Automatic Post-Call Feedback

The SDK automatically fetches detailed call information after each call ends and completes post-call processing. This includes comprehensive feedback such as transcript, summary, evaluation metrics, strengths, weaknesses, and ratings.

How It Works

When a call ends, the SDK automatically:

  1. Triggers the call-end event
  2. Processes the call on the backend (post-call analysis)
  3. Fetches complete call details
  4. Emits the call-details-ready event with full CallDTO data

This happens automatically—you don't need to manually call getCallDetails() after each call.

Event Lifecycle

const call = await client.startCall(simulationId);

// 1. Call begins
call.on('call-start', () => {
  console.log('Call started');
});

// 2. Real-time transcript messages during the call
call.on('message', message => {
  console.log('Real-time:', message.transcript);
});

// 3. Call ends
call.on('call-end', () => {
  console.log('Call ended - processing feedback...');
});

// 4. Post-call details are automatically fetched and ready
call.on('call-details-ready', callDetails => {
  console.log('Post-call feedback is ready!');

  // Access all call details
  console.log('Summary:', callDetails.summary);
  console.log('Full Transcript:', callDetails.transcript);
  console.log('Call Duration:', callDetails.callDurationMs, 'ms');
  console.log('Success:', callDetails.successEvaluation);
  console.log('Score:', callDetails.score);

  // Display evaluation metrics
  console.log(`${callDetails.metric1}: ${callDetails.metric1Value}`);
  console.log(`${callDetails.metric2}: ${callDetails.metric2Value}`);
  console.log(`${callDetails.metric3}: ${callDetails.metric3Value}`);

  // Show strengths and areas for improvement
  callDetails.strengths?.forEach(strength => {
    console.log('✓ Strength:', strength);
  });

  callDetails.weaknesses?.forEach(weakness => {
    console.log('⚠ Area to improve:', weakness);
  });

  // Access recording
  if (callDetails.recordingUrl) {
    console.log('Recording:', callDetails.recordingUrl);
  }
});

Practical Use Cases

1. Display Post-Call Feedback in UI

// Angular/React example
async startCall() {
  const call = await this.client.startCall(this.simulationId);

  // Automatically update UI when feedback is ready
  call.on('call-details-ready', callDetails => {
    this.callSummary = callDetails.summary;
    this.callScore = callDetails.score;
    this.strengths = callDetails.strengths;
    this.weaknesses = callDetails.weaknesses;
    this.showFeedbackModal = true; // Display feedback to user
  });
}

2. Track Performance Analytics

call.on('call-details-ready', callDetails => {
  // Send analytics to tracking service
  analytics.track('call_completed', {
    callId: callDetails._id,
    duration: callDetails.callDurationMs,
    score: callDetails.score,
    success: callDetails.successEvaluation,
    metrics: {
      [callDetails.metric1]: callDetails.metric1Value,
      [callDetails.metric2]: callDetails.metric2Value,
      [callDetails.metric3]: callDetails.metric3Value,
    },
  });
});

3. Store Call History

call.on('call-details-ready', async callDetails => {
  // Save to local storage or database
  const callHistory = JSON.parse(localStorage.getItem('call_history') || '[]');
  callHistory.push({
    id: callDetails._id,
    date: callDetails.createdAt,
    summary: callDetails.summary,
    score: callDetails.score,
  });
  localStorage.setItem('call_history', JSON.stringify(callHistory));
});

4. Generate Learning Insights

call.on('call-details-ready', callDetails => {
  // Aggregate insights across multiple calls
  if (callDetails.successEvaluation) {
    this.successfulCalls++;
    this.successStrategies.push(...(callDetails.strengths || []));
  } else {
    this.areasToImprove.push(...(callDetails.weaknesses || []));
  }

  this.updateLearningDashboard();
});

Manual Fetching (Alternative Approach)

While the SDK automatically fetches call details after each call, you can also manually retrieve them later using the call ID:

// Get call details at any time
const callDetails = await client.getCallDetails(callId);

This is useful when:

  • Viewing historical calls
  • Refreshing data after updates
  • Building a call history view

Error Handling

If fetching call details fails after a call ends, the error is logged but does not prevent the call-end event from completing. The call will still end gracefully.

call.on('call-end', () => {
  console.log('Call ended successfully');
  // This will always fire, even if call-details-ready fails
});

call.on('call-details-ready', callDetails => {
  // This may not fire if post-call processing fails
  // In that case, you can manually fetch later using getCallDetails()
});

Best Practices

  1. Always subscribe to call-details-ready to capture post-call feedback automatically
  2. Show loading state between call-end and call-details-ready events
  3. Handle missing data gracefully - some fields may be null or undefined depending on call status
  4. Store call IDs for later retrieval using getCallDetails() if needed
  5. Use the event data to provide immediate feedback to users rather than making additional API calls

Timing Considerations

  • call-end: Fires immediately when the call stops
  • call-details-ready: Fires after backend processing completes (typically 2-5 seconds after call ends)

Plan your UX accordingly—show a loading/processing state between these two events.

Data Types

CallDTO

Complete call details returned by call-details-ready event and getCallDetails() method:

interface CallDTO {
  _id: string; // MongoDB ID of the call
  callId: string; // Vapi call ID
  assistantId: string; // ID of the AI assistant used
  summary?: string; // AI-generated summary of the conversation
  transcript?: string; // Full transcript of the call
  recordingUrl?: string; // URL to the call recording
  rating?: number; // User-provided rating (0-5)
  successEvaluation?: boolean; // Whether the call was successful
  score?: number; // Overall score for the call
  strengths?: string[]; // Array of identified strengths
  weaknesses?: string[]; // Array of areas for improvement
  metric1?: string; // Name of evaluation metric 1
  metric1Value?: number; // Value for metric 1
  metric2?: string; // Name of evaluation metric 2
  metric2Value?: number; // Value for metric 2
  metric3?: string; // Name of evaluation metric 3
  metric3Value?: number; // Value for metric 3
  createdAt: Date; // When the call was created
  endedAt?: Date; // When the call ended
  callDurationMs?: number; // Duration in milliseconds
  // Additional fields may be present depending on call status
}

CreateSimulationDto

Configuration for creating a simulation:

interface CreateSimulationDto {
  persona: CharacterCreateDto;
  product: ProductConfig;
  presentation?: string;
  scenario: string;
  objectives?: string;
  anticipatedObjections?: string;
  trainingConfiguration: TrainingConfigurationDto;
  imageId: string;
  avatarLanguage: AvatarLanguage;
}

CharacterCreateDto

AI persona configuration:

interface CharacterCreateDto {
  name: string;
  professionalProfile: ProfessionalProfileDto;
  segment: SegmentType;
  personalityAndBehaviour: PersonalityAndBehaviourDto;
  context: ContextDto;
  assistantGender?: AssistantVoiceGender;
}

SimulationDetailsDto

Detailed simulation information returned by getSimulationDetails():

interface SimulationDetailsDto {
  simulationId: string;
  phase: CreationPhase;
  assistantId: string;
  configuration?: CreateSimulationDto;
  createdAt: Date;
  updatedAt: Date;
}

CreationPhase

Enum representing the simulation creation phases:

enum CreationPhase {
  STARTING = 'STARTING',
  CORE = 'CORE',
  BOUNDARIES = 'BOUNDARIES',
  SPEECH_AND_THOUGHT = 'SPEECH_AND_THOUGHT',
  CONVERSATION_EVOLUTION = 'CONVERSATION_EVOLUTION',
  MEMORY = 'MEMORY',
  FINISHED = 'FINISHED',
  ERROR = 'ERROR',
}

RecommendationsResponseDto

Response object from getRecommendations():

interface RecommendationsResponseDto {
  recommendations: RecommendationItemDto[];
  strengths: PerformancePatternDto[];
  improvementAreas: ImprovementAreaDto[];
  summary: string;
  callsAnalyzed: number;
  generatedAt: Date;
}

interface RecommendationItemDto {
  title: string;
  description: string;
  priority: 'high' | 'medium' | 'low';
}

interface PerformancePatternDto {
  strength: string;
  frequency: number;
}

interface ImprovementAreaDto {
  area: string;
  frequency: number;
}

PdfSlidesDto

Simplified data structure for PDF slide records (used in lists):

interface PdfSlidesDto {
  _id: string;
  originalFilename: string;
  totalSlides?: number;
  lessonOverview?: string;
  createdAt: Date;
  updatedAt: Date;
}

PdfSlideDto

Detailed data structure for a single PDF slide analysis:

interface PdfSlideDto {
  _id: string;
  originalFilename: string;
  totalSlides?: number;
  lessonOverview?: string;
  slideAnalysis?: Array<{
    slideNumber: number;
    title: string;
    textExtraction: string;
  }>;
  createdAt: Date;
  updatedAt: Date;
}

PdfSlidesAnalysisQueryDto

Query parameters for retrieving slide analysis records:

interface PdfSlidesAnalysisQueryDto {
  limit?: 5 | 10 | 25;
  page?: number;
  sort?: 'asc' | 'desc';
  sortBy?: 'createdAt' | 'originalFilename' | 'totalSlides';
}

AssistantImageDto

Data structure for assistant images:

interface AssistantImageDto {
  _id: string;
  imageUrl: string;
}

AvatarLanguage

Enum representing available avatar languages:

enum AvatarLanguage {
  English = 'en',
  German = 'de',
  Spanish = 'es',
  Italian = 'it',
  French = 'fr',
}

Error Handling

The SDK throws errors for invalid configurations and API failures:

try {
  const client = new PlatoApiClient({
    baseUrl: 'https://api.plato.com',
    token: 'your-token',
    user: 'your-user',
  });

  const simulation = await client.createSimulation(simulationConfig);
  const call = await client.startCall(simulation.simulationId);
} catch (error) {
  console.error('Error:', error.message);
}

Support

For support and questions, please contact the development team.