Setting the file. One moment.
Subchapter 114.43
use-cases/minutes-calculation.mdMarkdown23 KBView on GitHub
Calculate usage minutes for Video SDK sessions and Meeting SDK meetings for billing and cost management.
References
App TypesZoom SDKs are billed based on participant-minutes. This guide covers how to track and calculate usage for accurate billing projections and cost optimization.
| SDK | Billing Unit | Calculation |
|---|---|---|
| Video SDK | Participant-minutes | Sum of (each participant’s session duration) |
| Meeting SDK | Host minutes | Based on meeting duration, not participant count |
Example: A 30-minute Video SDK session with 4 participants = 120 participant-minutes.
Most accurate method using Zoom’s built-in analytics.
const axios = require('axios');
// Get session details including participant minutes
async function getSessionUsage(sessionId) {
const response = await axios.get(
`https://api.zoom.us/v2/videosdk/sessions/${sessionId}`,
{
headers: { 'Authorization': `Bearer ${accessToken}` }
}
);
return {
sessionName: response.data.session_name,
startTime: response.data.start_time,
endTime: response.data.end_time,
totalMinutes: response.data.duration, // Total session minutes
participantCount: response.data.participant_count
};
}
// Get participant-level breakdown
async function getSessionParticipants(sessionId) {
const response = await axios.get(
`https://api.zoom.us/v2/videosdk/sessions/${sessionId}/participants`,
{
params: { page_size: 300 },
headers: { 'Authorization': `Bearer ${accessToken}` }
}
);
// Calculate participant-minutes
const participants = response.data.participants.map(p => {
const joinTime = new Date(p.join_time);
const leaveTime = new Date(p.leave_time);
const durationMinutes = (leaveTime - joinTime) / 1000 / 60;
return {
name: p.user_name,
joinTime: p.join_time,
leaveTime: p.leave_time,
durationMinutes: Math.round(durationMinutes * 100) / 100
};
});
const totalParticipantMinutes = participants.reduce(
(sum, p) => sum + p.durationMinutes, 0
);
return {
participants,
totalParticipantMinutes: Math.round(totalParticipantMinutes * 100) / 100
};
}
// Get all sessions in date range
async function getSessionsInRange(from, to) {
const response = await axios.get(
'https://api.zoom.us/v2/videosdk/sessions',
{
params: { from, to, page_size: 300 },
headers: { 'Authorization': `Bearer ${accessToken}` }
}
);
return response.data.sessions;
}
// Calculate monthly usage
async function calculateMonthlyUsage(year, month) {
const from = `${year}-${String(month).padStart(2, '0')}-01`;
const lastDay = new Date(year, month, 0).getDate();
const to = `${year}-${String(month).padStart(2, '0')}-${lastDay}`;
const sessions = await getSessionsInRange(from, to);
let totalParticipantMinutes = 0;
const sessionDetails = [];
for (const session of sessions) {
const participants = await getSessionParticipants(session.id);
totalParticipantMinutes += participants.totalParticipantMinutes;
sessionDetails.push({
sessionId: session.id,
sessionName: session.session_name,
date: session.start_time,
participantMinutes: participants.totalParticipantMinutes
});
}
return {
period: `${year}-${String(month).padStart(2, '0')}`,
totalSessions: sessions.length,
totalParticipantMinutes: Math.round(totalParticipantMinutes),
sessions: sessionDetails
};
}Track usage in real-time as sessions happen.
const express = require('express');
const app = express();
// In-memory storage (use database in production)
const activeSessions = new Map();
const usageRecords = [];
app.post('/webhook', express.json(), (req, res) => {
const { event
Track locally in your SDK application.
// React Native / JavaScript SDK tracking
class UsageTracker {
constructor() {
this.sessionStart = null;
this.participants = new Map();
}
onSessionJoin() {
this.sessionStart = new Date();
}
onUserJoin(user) {
this.participants.set(user.id, {
name: user.name,
joinTime: new Date(),
leaveTime: null
});
}
onUserLeave(user) {
const participant = this.participants.get(user.id);
if (participant) {
participant.leaveTime = new Date();
}
}
calculateUsage() {
const now = new Date();
let totalMinutes = 0;
this.participants.forEach(participant => {
const endTime = participant.leaveTime || now;
const duration = (endTime - participant.joinTime) / 1000 / 60;
totalMinutes += duration;
});
return {
sessionDuration: (now - this.sessionStart) / 1000 / 60,
totalParticipantMinutes: Math.round(totalMinutes * 100) / 100,
participantCount: this.participants.size
};
}
// Send to your backend periodically
async reportUsage() {
const usage = this.calculateUsage();
await fetch('/api/usage', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(usage)
});
}
}// Windows/C++ SDK tracking
class UsageTracker : public IZoomVideoSDKDelegate {
private:
std::chrono::system_clock::time_point sessionStart;
struct ParticipantUsage {
std::wstring name;
std::chrono::system_clock::time_point joinTime;
std::chrono::system_clock::time_point leaveTime;
bool hasLeft = false;
};
std::map<std::wstring, ParticipantUsage> participants;
public:
void onSessionJoin() override {
sessionStart = std::chrono::system_clock::now();
}
void onUserJoin(IZoomVideoSDKUserHelper* helper,
IVideoSDKVector<IZoomVideoSDKUser*>* users) override {
for (int i = 0; i < users->GetCount(); i++) {
auto user = users->GetItem(i);
ParticipantUsage usage;
usage.name = user->getUserName();
usage.joinTime = std::chrono::system_clock::now();
participants[user->getUserID()] = usage;
}
}
void onUserLeave(IZoomVideoSDKUserHelper* helper,
IVideoSDKVector<IZoomVideoSDKUser*>* users) override {
for (int i = 0; i < users->GetCount(); i++) {
auto user = users->GetItem(i);
auto it = participants.find(user->getUserID());
if (it != participants.end()) {
it->second.leaveTime = std::chrono::system_clock::now();
it->second.hasLeft = true;
}
}
}
double calculateTotalMinutes() {
auto now = std::chrono::system_clock::now();
double totalMinutes = 0;
for (const auto& [id, participant] : participants) {
auto endTime = participant.hasLeft ? participant.leaveTime : now;
auto duration = std::chrono::duration_cast<std::chrono::minutes>(
endTime - participant.joinTime
);
totalMinutes += duration.count();
}
return totalMinutes;
}
};// Get meeting usage from Reports API
async function getMeetingUsage(meetingId) {
// Note: Double-encode UUID if it contains / or //
const encodedId = meetingId.includes('/')
? encodeURIComponent(encodeURIComponent(meetingId))
: meetingId;
const [meeting, participants] = await Promise.all([
axios.get(
`https://api.zoom.us/v2/report/meetings/${encodedId}`,
{ headers: { 'Authorization': `Bearer ${accessToken}` }}
),
axios.get(
`https://api.zoom.us/v2/report/meetings/${encodedId}/participants`,
{
params: { page_size: 300 },
headers: { 'Authorization': `Bearer ${accessToken}` }
}
)
]);
// Calculate participant-minutes
const participantMinutes = participants.data.participants.map(p => {
const duration = p.duration; // Already in seconds
return {
name: p.name,
email: p.user_email,
durationMinutes: Math.round(duration / 60 * 100) / 100
};
});
const totalParticipantMinutes = participantMinutes.reduce(
(sum, p) => sum + p.durationMinutes, 0
);
return {
meetingId: meetingId,
topic: meeting.data.topic,
startTime: meeting.data.start_time,
endTime: meeting.data.end_time,
hostMinutes: meeting.data.duration, // Meeting duration in minutes
totalParticipantMinutes: Math.round(totalParticipantMinutes),
participantCount: participants.data.participants.length,
participants: participantMinutes
};
}
// Get daily usage summary
async function getDailyUsageSummary(year, month) {
const response = await axios.get(
'https://api.zoom.us/v2/report/daily',
{
params: { year, month },
headers: { 'Authorization': `Bearer ${accessToken}` }
}
);
// Aggregate daily data
const summary = response.data.dates.reduce((acc, day) => {
acc.totalMeetings += day.meetings;
acc.totalMinutes += day.meeting_minutes;
acc.totalParticipants += day.participants;
return acc;
}, { totalMeetings: 0, totalMinutes: 0, totalParticipants: 0 });
return {
period: `${year}-${String(month).padStart(2, '0')}`,
...summary,
dailyBreakdown: response.data.dates
};
}
// Get user-specific meeting report
async function getUserMeetingReport(userId, from, to) {
const response = await axios.get(
`https://api.zoom.us/v2/report/users/${userId}/meetings`,
{
params: { from, to, page_size: 300 },
headers: { 'Authorization': `Bearer ${accessToken}` }
}
);
const totalMinutes = response.data.meetings.reduce(
(sum, m) => sum + m.duration, 0
);
return {
userId,
period: { from, to },
totalMeetings: response.data.meetings.length,
totalHostMinutes: totalMinutes,
meetings: response.data.meetings
};
}// Meeting SDK webhook events
app.post('/meeting-webhook', express.json(), (req, res) => {
const { event, payload } = req.body;
switch (event) {
case 'meeting.started':
handleMeetingStarted(payload);
break;
case 'meeting.ended':
handleMeetingEnded(payload);
break;
case 'meeting.participant_joined':
handleMeetingParticipantJoined(payload);
break;
case 'meeting.participant_left':
handleMeetingParticipantLeft(payload);
break;
}
res.status(200).send();
});
// Store in database for billing
async function handleMeetingEnded(payload) {
const { object } = payload;
await db.meetings.insert({
meetingId: object.id,
uuid: object.uuid,
topic: object.topic,
hostId: object.host_id,
startTime: object.start_time,
endTime: object.end_time,
durationMinutes: object.duration,
participantCount: object.participant_count
});
}// Pricing tiers (example - check current Zoom pricing)
const PRICING_TIERS = [
{ upTo: 10000, pricePerMinute: 0.0050 },
{ upTo: 50000, pricePerMinute: 0.0040 },
{ upTo: 100000, pricePerMinute: 0.0030 },
{ upTo: Infinity, pricePerMinute: 0.0025 }
];
function estimateMonthlyCost(participantMinutes) {
let remaining = participantMinutes;
let totalCost = 0;
let previousLimit = 0;
for (const tier of PRICING_TIERS) {
const tierMinutes = Math.min(remaining, tier.upTo - previousLimit);
if (tierMinutes <= 0) break;
totalCost += tierMinutes * tier.pricePerMinute;
remaining -= tierMinutes;
previousLimit = tier.upTo;
}
return {
participantMinutes,
estimatedCost: Math.round(totalCost * 100) / 100,
currency: 'USD'
};
}
// Project usage for the month
function projectMonthlyUsage(currentUsage, dayOfMonth, daysInMonth) {
const dailyAverage = currentUsage / dayOfMonth;
const projectedTotal = dailyAverage * daysInMonth;
return {
currentUsage,
dailyAverage: Math.round(dailyAverage),
projectedMonthlyUsage: Math.round(projectedTotal),
projectedCost: estimateMonthlyCost(projectedTotal)
};
}Calculate cumulative usage from the start of the year.
// Calculate YTD usage for Video SDK
async function getYTDUsage() {
const now = new Date();
const year = now.getFullYear();
const currentMonth = now.getMonth() + 1;
// Fetch all months in parallel
const monthPromises = [];
for (let month = 1; month
// Build a usage dashboard
async function getUsageDashboard() {
const now = new Date();
const year = now.getFullYear();
const month = now.getMonth() + 1;
const dayOfMonth = now.getDate();
const daysInMonth = new Date(year, month, 0).getDate();
// Get current month usage
const usage = await calculateMonthlyUsage(year, month);
// Calculate projections
const projection = projectMonthlyUsage(
usage.totalParticipantMinutes,
dayOfMonth,
daysInMonth
);
// Get previous month for comparison
const prevMonth = month === 1 ? 12 : month - 1;
const prevYear = month === 1 ? year - 1 : year;
const previousUsage = await calculateMonthlyUsage(prevYear, prevMonth);
return {
currentMonth: {
period: usage.period,
totalSessions: usage.totalSessions,
totalParticipantMinutes: usage.totalParticipantMinutes,
estimatedCost: estimateMonthlyCost(usage.totalParticipantMinutes)
},
projection: {
projectedMinutes: projection.projectedMonthlyUsage,
projectedCost: projection.projectedCost,
dailyAverage: projection.dailyAverage
},
previousMonth: {
period: previousUsage.period,
totalParticipantMinutes: previousUsage.totalParticipantMinutes,
monthOverMonthChange: (
(usage.totalParticipantMinutes - previousUsage.totalParticipantMinutes) /
previousUsage.totalParticipantMinutes * 100
).toFixed(1) + '%'
},
topSessions: usage.sessions
.sort((a, b) => b.participantMinutes - a.participantMinutes)
.slice(0, 10)
};
}| Data Source | Retention Period |
|---|---|
| Session Quality API | 30 days |
| Reports API (meetings) | 12 months |
| Reports API (participants) | 1 month |
| Webhook events | Store your own |
usage-reporting-analytics.mdwebhooks skill