Setting the file. One moment.
Subchapter 114.67
use-cases/user-and-meeting-creation.mdMarkdown17 KBView on GitHub
Create a user account and immediately schedule a meeting for that user.
References
App TypesA common provisioning pattern: create a new Zoom user via REST API, wait for activation, then create a meeting for that user. This requires understanding user states and proper sequencing.
| Order | Skill | Purpose |
|---|---|---|
| 1 | zoom-rest-api | Create user account |
| 2 | zoom-rest-api | Create meeting for user |
| (Optional) | webhooks | Receive user activation event |
┌─────────────────────────────────────────────────────────────────────────┐
│ USER + MEETING CREATION FLOW │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ 1. Create User (zoom-rest-api) │
│ └── POST /users │
│ └── action: "create" | "autoCreate" | "custCreate" | "ssoCreate" │
│ └── Returns: user_id, status │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ 2. Wait for User Activation │
│ └── Option A: Poll GET /users/{userId} until status = "active" │
│ └── Option B: Listen for user.activated webhook │
│ └── Option C: Use autoCreate (auto-activates) │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ 3. Create Meeting (zoom-rest-api) │
│ └── POST /users/{userId}/meetings │
│ └── Returns: meeting_id, join_url, start_url │
└─────────────────────────────────────────────────────────────────────────┘user:write:admin, meeting:write:admin| Action | Description | Activation | Best For |
|---|---|---|---|
create | Sends activation email to user | User clicks email link | Standard provisioning |
autoCreate | Creates pre-activated user | Immediate | Automated systems |
custCreate | Creates user without email | Manual activation | Custom workflows |
ssoCreate | Creates SSO user | SSO login | Enterprise SSO |
const axios = require('axios');
/**
* Create a new Zoom user
* @param {Object} userInfo - User information
* @param {string} accessToken - Valid OAuth access token
* @returns {Promise<Object>} Created user details
*/
async function createUser(userInfo, accessToken) {
try {
const response = await axios.post(
'https://api.zoom.us/v2/users',
{
action: 'create', // Sends activation email
user_info: {
email: userInfo.email,
type: userInfo.type || 1, // 1=Basic, 2=Licensed
first_name: userInfo.firstName,
last_name: userInfo.lastName,
password: userInfo.password // Optional
}
},
{
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
}
);
return {
id: response.data.id,
email: response.data.email,
first_name: response.data.first_name,
last_name: response.data.last_name,
type: response.data.type,
status: 'pending' // User needs to activate via email
};
} catch (error) {
if (error.response?.status === 409) {
throw new Error(`User ${userInfo.email} already exists`);
}
if (error.response?.status === 400) {
throw new Error(`Invalid user data: ${error.response.data.message}`);
}
throw error;
}
}/**
* Create a pre-activated Zoom user (no email required)
* Recommended for automated provisioning
*/
async function createUserAutoActivate(userInfo, accessToken) {
try {
const response = await axios.post(
'https://api.zoom.us/v2/users',
{
action: 'autoCreate', // User is immediately active
user_info: {
email: userInfo.email,
type: userInfo.type || 2, // 2=Licensed (required for autoCreate)
first_name: userInfo.firstName,
last_name: userInfo.lastName,
password: userInfo.password || generateSecurePassword()
}
},
{
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
}
);
return {
id: response.data.id,
email: response.data.email,
first_name: response.data.first_name,
last_name: response.data.last_name,
type: response.data.type,
status: 'active' // Ready immediately
};
} catch (error) {
handleUserCreationError(error, userInfo.email);
}
}
function generateSecurePassword() {
const crypto = require('crypto');
return crypto.randomBytes(16).toString('base64') + '!Aa1';
}/**
* Wait for user to become active by polling
* @param {string} userId - User ID to check
* @param {string} accessToken - OAuth token
* @param {number} maxWaitMs - Maximum wait time (default 5 minutes)
* @param {number} pollIntervalMs - Poll interval (default 5 seconds)
*/
async function waitForUserActivation(userId, accessToken, maxWaitMs = 300000, pollIntervalMs = 5000) {
const startTime = Date.now();
while (Date.now() - startTime < maxWaitMs) {
const user = await getUser(userId, accessToken);
if (user.status === 'active') {
console.log(`User ${userId} is now active`);
return user;
}
console.log(`User ${userId} status: ${user.status}, waiting...`);
await new Promise(resolve => setTimeout(resolve, pollIntervalMs));
}
throw new Error(`User ${userId} did not activate within ${maxWaitMs}ms`);
}
async function getUser(userId, accessToken) {
const response = await axios.get(
`https://api.zoom.us/v2/users/${userId}`,
{
headers: { 'Authorization': `Bearer ${accessToken}` }
}
);
return response.data;
}const express = require('express');
const app = express();
app.use(express.json());
// Store pending user creations
const pendingUsers = new Map();
/**
* Create user and wait for webhook activation
*/
async function createUserAndWait(userInfo, accessToken) {
// Create user
const user = await createUser(userInfo, accessToken);
// Set up promise that resolves when webhook fires
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
pendingUsers.delete(user.id);
reject(new Error(`User ${user.id} activation timeout`));
}, 300000); // 5 minute timeout
pendingUsers.set(user.id, { resolve, reject, timeout, user });
});
}
// Webhook handler for user activation
app.post('/webhooks/zoom', (req, res) => {
const { event, payload } = req.body;
if (event === 'user.activated') {
const userId = payload.object.id;
const pending = pendingUsers.get(userId);
if (pending) {
clearTimeout(pending.timeout);
pendingUsers.delete(userId);
pending.resolve({ ...pending.user, status: 'active' });
}
}
res.status(200).send();
});/**
* Create a meeting for a specific user
* @param {string} userId - User ID or email
* @param {Object} meetingInfo - Meeting details
* @param {string} accessToken - OAuth token
*/
async function createMeetingForUser(userId, meetingInfo, accessToken) {
try {
const response = await axios.post(
`https://api.zoom.us/v2/users/${userId}/meetings`,
{
topic: meetingInfo.topic,
type: meetingInfo.type || 2, // 2 = Scheduled
start_time: meetingInfo.startTime,
duration: meetingInfo.duration || 60,
timezone: meetingInfo.timezone || 'UTC',
agenda: meetingInfo.agenda,
settings: {
host_video: true,
participant_video: true,
join_before_host: false,
mute_upon_entry: true,
waiting_room: true,
...meetingInfo.settings
}
},
{
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
}
);
return {
id: response.data.id,
topic: response.data.topic,
start_time: response.data.start_time,
join_url: response.data.join_url,
start_url: response.data.start_url,
password: response.data.password,
host_id: response.data.host_id,
host_email: response.data.host_email
};
} catch (error) {
if (error.response?.status === 404) {
throw new Error(`User ${userId} not found or not active`);
}
if (error.response?.status === 429) {
throw new Error('Rate limit exceeded. Try again later.');
}
throw error;
}
}/**
* Complete example: Create user and schedule their first meeting
*
* This demonstrates skill chaining:
* 1. zoom-rest-api (users) - Create user
* 2. zoom-rest-api (meetings) - Create meeting
*/
async function provisionUserWithMeeting(userInfo, meetingInfo) {
console.log('Starting user provisioning...');
// Get access token
const accessToken = await getAccessToken();
try {
// Step 1: Create user (autoCreate for immediate activation)
/**
* Robust provisioning with rollback capability
*/
async function provisionUserWithMeetingSafe(userInfo, meetingInfo) {
const accessToken = await getAccessToken();
let createdUser = null;
try {
// Step 1: Create user
createdUser = await createUserAutoActivate(userInfo, accessToken);
// Step 2: Create meeting
const meeting = await createMeetingForUser(createdUser.id, meetingInfo, accessToken);
return { success: true, user: createdUser, meeting };
} catch (error) {
// Rollback: Delete user if meeting creation failed
if (createdUser && error.message.includes('meeting')) {
console.log(`Rolling back: deleting user ${createdUser.id}`);
try {
await deleteUser(createdUser.id, accessToken);
} catch (deleteError) {
console.error('Rollback failed:', deleteError.message);
}
}
throw error;
}
}
async function deleteUser(userId, accessToken) {
await axios.delete(
`https://api.zoom.us/v2/users/${userId}?action=delete`,
{
headers: { 'Authorization': `Bearer ${accessToken}` }
}
);
}| Error | Cause | Solution |
|---|---|---|
| 409 Conflict | User email already exists | Use existing user or different email |
| 400 Bad Request | Invalid user data | Check email format, required fields |
| 404 Not Found | User not active/found | Wait for activation or verify user ID |
| 429 Rate Limit | Too many requests | Implement backoff, batch operations |
| 201 but pending | User needs to activate | Use autoCreate or wait for activation |
| Type | Value | Description | Can Host? |
|---|---|---|---|
| Basic | 1 | Free user | Limited |
| Licensed | 2 | Paid license | Yes |
| On-prem | 3 | On-premise deployment | Yes |
| None | 99 | No license | No |