First create the recurring event
What you will build
A recurring event series with automated scheduling, unified registration, and per-occurrence management.
Use cases covered
- Weekly team standup meetings, occurring every Monday at 10 a.m. for 12 weeks.
- Monthly webinar series occurring on the first Tuesday of each month for six months.
- Custom recurring patterns with flexible registration options.
Time required
15 minutes
What you will learn
By the end of this tutorial, you will know how to:
- Create recurring event series with daily, weekly, or monthly patterns.
- Configure different registration options (series-level vs per-occurrence).
- List and manage individual occurrences.
- Register attendees for specific sessions or entire series.
Prerequisites
Before starting, ensure you have:
- Account: Pro or higher plan
- License: Zoom Webinars Plus or Events
- API access: OAuth app with
zoom_events:write:eventandzoom_events:read:list_sessionsscopes - Hub access: Host role in at least one hub
- Access token: Valid OAuth token for authentication
Scenario 1: Weekly team standup series
Create a recurring standup meeting that runs every Monday at 10am Eastern Time for 12 weeks (Q1 2025).
Step 1: Get your Hub ID
First, retrieve the hub where you will create the event.
cURL
curl -X GET 'https://api.zoom.us/v2/zoom_events/hubs?role_type=host' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
Python
import requests
url = "https://api.zoom.us/v2/zoom_events/hubs"
headers = {
"Authorization": "Bearer YOUR_ACCESS_TOKEN"
}
params = {"role_type": "host"}
response = requests.get(url, headers=headers, params=params)
response.raise_for_status() # Check for HTTP errors
hubs = response.json()
hub_id = hubs['hubs'][0]['hub_id']
print(f"Hub ID: {hub_id}")
JavaScript (Node.js)
const axios = require("axios");
async function getHubId() {
try {
const response = await axios.get(
"https://api.zoom.us/v2/zoom_events/hubs",
{
headers: { Authorization: "Bearer YOUR_ACCESS_TOKEN" },
params: { role_type: "host" },
},
);
const hubId = response.data.hubs[0].hub_id;
console.log(`Hub ID: ${hubId}`);
return hubId;
} catch (error) {
console.error(
"Failed to get hub ID:",
error.response?.data || error.message,
);
throw error;
}
}
Response
{
"total_records": 1,
"hubs": [
{
"hub_id": "4uzfv3JwTeyR5QpC3PXwMg",
"name": "Engineering Team Hub"
}
]
}
Save the hub_id value—you'll need it in the next step.
Step 2: Create the weekly recurring event
Now create a recurring event with a weekly pattern. The event starts January 6, 2025 (first Monday) and repeats every Monday for 12 weeks.
Understanding the recurrence pattern:
type: 2= Weekly recurrencerepeat_interval: 1= Every 1 week (not every 2 weeks)monthly_week_day: 1= Monday (0=Sunday, 1=Monday, 2=Tuesday, etc.)end_times: 12= Generate 12 occurrencesduration: 30= Each session lasts 30 minutes
cURL
curl -X POST 'https://api.zoom.us/v2/zoom_events/events' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-d '{
"hub_id": "4uzfv3JwTeyR5QpC3PXwMg",
"name": "Engineering Team Weekly Standup",
"timezone": "America/New_York",
"event_type": "RECURRING",
"access_level": "PRIVATE_UNRESTRICTED",
"meeting_type": "MEETING",
"attendance_type": "VIRTUAL",
"calendar": [
{
"start_time": "2025-01-06T15:00:00Z"
}
],
"recurrence": {
"type": 2,
"repeat_interval": 1,
"monthly_week_day": 1,
"end_times": 12,
"duration": 30
}
}'
Python
import requests
from datetime import datetime
url = "https://api.zoom.us/v2/zoom_events/events"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_ACCESS_TOKEN"
}
payload = {
"hub_id": hub_id,
"name": "Engineering Team Weekly Standup",
"timezone": "America/New_York",
"event_type": "RECURRING",
"access_level": "PRIVATE_UNRESTRICTED",
"meeting_type": "MEETING",
"attendance_type": "VIRTUAL",
"calendar": [
{
"start_time": "2025-01-06T15:00:00Z" # 10am ET = 3pm UTC
}
],
"recurrence": {
"type": 2, # Weekly
"repeat_interval": 1, # Every week
"monthly_week_day": 1, # Monday
"end_times": 12, # 12 occurrences
"duration": 30 # 30 minutes each
}
}
response = requests.post(url, headers=headers, json=payload)
response.raise_for_status() # Check for HTTP errors
event_data = response.json()
event_id = event_data['event_id']
print(f"Created recurring event: {event_id}")
print(f"Event URL: {event_data['event_url']}")
JavaScript (Node.js)
const axios = require("axios");
async function createWeeklyStandup(hubId) {
try {
const response = await axios.post(
"https://api.zoom.us/v2/zoom_events/events",
{
hub_id: hubId,
name: "Engineering Team Weekly Standup",
timezone: "America/New_York",
event_type: "RECURRING",
access_level: "PRIVATE_UNRESTRICTED",
meeting_type: "MEETING",
attendance_type: "VIRTUAL",
calendar: [
{
start_time: "2025-01-06T15:00:00Z",
},
],
recurrence: {
type: 2, // Weekly
repeat_interval: 1, // Every week
monthly_week_day: 1, // Monday
end_times: 12, // 12 occurrences
duration: 30, // 30 minutes each
},
},
{
headers: {
"Content-Type": "application/json",
Authorization: "Bearer YOUR_ACCESS_TOKEN",
},
},
);
console.log(`Event ID: ${response.data.event_id}`);
console.log(`Event URL: ${response.data.event_url}`);
return response.data.event_id;
} catch (error) {
console.error(
"Failed to create recurring event:",
error.response?.data || error.message,
);
throw error;
}
}
Response
{
"event_id": "kNqLPC6hSFiZ9NpgjA549w",
"name": "Engineering Team Weekly Standup",
"event_type": "RECURRING",
"status": "draft",
"event_url": "https://events.zoom.us/e/kNqLPC6hSFiZ9NpgjA549w",
"calendar": [
{
"start_time": "2025-01-06T15:00:00Z"
}
],
"recurrence": {
"type": 2,
"repeat_interval": 1,
"monthly_week_day": 1,
"end_times": 12,
"duration": 30
}
}
The system automatically generates 12 Monday occurrences starting January 6, 2025.
Step 3: Create a Ticket Type for registration
Before publishing, create a ticket type (required even for free events).
cURL
curl -X POST 'https://api.zoom.us/v2/zoom_events/events/kNqLPC6hSFiZ9NpgjA549w/ticket_types' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-d '{
"name": "Team Member Pass",
"free": true,
"quantity": 50
}'
Python
ticket_url = f"https://api.zoom.us/v2/zoom_events/events/{event_id}/ticket_types"
ticket_payload = {
"name": "Team Member Pass",
"free": True,
"quantity": 50
}
ticket_response = requests.post(ticket_url, headers=headers, json=ticket_payload)
ticket_response.raise_for_status() # Check for HTTP errors
ticket_type_id = ticket_response.json()['ticket_type_id']
print(f"Ticket Type ID: {ticket_type_id}")
JavaScript
async function createTicketType(eventId) {
try {
const response = await axios.post(
`https://api.zoom.us/v2/zoom_events/events/${eventId}/ticket_types`,
{
name: "Team Member Pass",
free: true,
quantity: 50,
},
{
headers: {
"Content-Type": "application/json",
Authorization: "Bearer YOUR_ACCESS_TOKEN",
},
},
);
return response.data.ticket_type_id;
} catch (error) {
console.error(
"Failed to create ticket type:",
error.response?.data || error.message,
);
throw error;
}
}
Step 4: Configure registration access link
Create an access link with registration option. For this standup series, we'll use "all_sessions" registration so team members register once for the entire quarter.
cURL
curl -X POST 'https://api.zoom.us/v2/zoom_events/events/kNqLPC6hSFiZ9NpgjA549w/access_links' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-d '{
"name": "Team Registration Link",
"type": "registration",
"is_default": true,
"ticket_type_id": "TICKET_TYPE_ID",
"recurring_registration_option": "all_sessions",
"authentication_method": "bypass_auth"
}'
Python
access_link_url = f"https://api.zoom.us/v2/zoom_events/events/{event_id}/access_links"
access_link_payload = {
"name": "Team Registration Link",
"type": "registration",
"is_default": True,
"ticket_type_id": ticket_type_id,
"recurring_registration_option": "all_sessions",
"authentication_method": "bypass_auth"
}
access_response = requests.post(access_link_url, headers=headers, json=access_link_payload)
access_response.raise_for_status() # Check for HTTP errors
registration_url = access_response.json()['registration_url']
print(f"Registration URL: {registration_url}")
JavaScript
async function createAccessLink(eventId, ticketTypeId) {
try {
const response = await axios.post(
`https://api.zoom.us/v2/zoom_events/events/${eventId}/access_links`,
{
name: "Team Registration Link",
type: "registration",
is_default: true,
ticket_type_id: ticketTypeId,
recurring_registration_option: "all_sessions",
authentication_method: "bypass_auth",
},
{
headers: {
"Content-Type": "application/json",
Authorization: "Bearer YOUR_ACCESS_TOKEN",
},
},
);
console.log(`Registration URL: ${response.data.registration_url}`);
return response.data;
} catch (error) {
console.error(
"Failed to create access link:",
error.response?.data || error.message,
);
throw error;
}
}
What "all_sessions" means
When an attendee registers once, they receive a single ticket granting access to all 12 Monday standup sessions.
Step 5: Publish the event
Make the event live and start accepting registrations.
cURL
curl -X POST 'https://api.zoom.us/v2/zoom_events/events/kNqLPC6hSFiZ9NpgjA549w/event_actions' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-d '{"operation": "publish"}'
Python
publish_url = f"https://api.zoom.us/v2/zoom_events/events/{event_id}/event_actions"
publish_payload = {"operation": "publish"}
publish_response = requests.post(publish_url, headers=headers, json=publish_payload)
publish_response.raise_for_status() # Check for HTTP errors
if publish_response.status_code == 200:
print("Event published successfully")
JavaScript
async function publishEvent(eventId) {
try {
await axios.post(
`https://api.zoom.us/v2/zoom_events/events/${eventId}/event_actions`,
{ operation: "publish" },
{
headers: {
"Content-Type": "application/json",
Authorization: "Bearer YOUR_ACCESS_TOKEN",
},
},
);
console.log("Event published");
} catch (error) {
console.error(
"Failed to publish event:",
error.response?.data || error.message,
);
throw error;
}
}
Step 6: Verify all occurrences were created
List all generated session occurrences to confirm the 12-week schedule.
cURL
curl -X GET 'https://api.zoom.us/v2/zoom_events/events/kNqLPC6hSFiZ9NpgjA549w/sessions' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
Python
sessions_url = f"https://api.zoom.us/v2/zoom_events/events/{event_id}/sessions"
sessions_response = requests.get(sessions_url, headers=headers)
sessions_response.raise_for_status() # Check for HTTP errors
sessions_data = sessions_response.json()
print(f"Total occurrences: {sessions_data['total_records']}")
for i, session in enumerate(sessions_data['sessions'], 1):
print(f"Week {i}: {session['start_time']} (Session ID: {session['session_id']})")
JavaScript
async function listSessions(eventId) {
try {
const response = await axios.get(
`https://api.zoom.us/v2/zoom_events/events/${eventId}/sessions`,
{ headers: { Authorization: "Bearer YOUR_ACCESS_TOKEN" } },
);
console.log(`Total occurrences: ${response.data.total_records}`);
response.data.sessions.forEach((session, index) => {
console.log(
`Week ${index + 1}: ${session.start_time} (ID: ${session.session_id})`,
);
});
return response.data.sessions;
} catch (error) {
console.error(
"Failed to list sessions:",
error.response?.data || error.message,
);
throw error;
}
}
Response
{
"total_records": 12,
"sessions": [
{
"session_id": "aVSyJswkTGC4o1gqVogTaA",
"start_time": "2025-01-06T15:00:00Z",
"end_time": "2025-01-06T15:30:00Z"
},
{
"session_id": "SPbxN1VwSrygHG0N6T2YtQ",
"start_time": "2025-01-13T15:00:00Z",
"end_time": "2025-01-13T15:30:00Z"
}
// ...
]
}
Now, all 12 Monday occurrences are scheduled.
Scenario 2: Monthly webinar series
Create a monthly webinar that runs on the first Tuesday of each month at 2pm Pacific Time for 6 months.
Create monthly recurring webinar
Understanding monthly patterns:
type: 3= Monthly recurrencerepeat_interval: 1= Every 1 monthmonthly_week: 1= First week of the month (1=first, 2=second, 3=third, 4=fourth)monthly_week_day: 2= Tuesday (0=Sunday, 1=Monday, 2=Tuesday, etc.)end_times: 6= Generate 6 monthly occurrencesduration: 90= Each webinar lasts 90 minutes
cURL
curl -X POST 'https://api.zoom.us/v2/zoom_events/events' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-d '{
"hub_id": "4uzfv3JwTeyR5QpC3PXwMg",
"name": "Product Updates Webinar Series",
"timezone": "America/Los_Angeles",
"event_type": "RECURRING",
"access_level": "PRIVATE_UNRESTRICTED",
"meeting_type": "WEBINAR",
"attendance_type": "VIRTUAL",
"calendar": [
{
"start_time": "2025-02-04T22:00:00Z"
}
],
"recurrence": {
"type": 3,
"repeat_interval": 1,
"monthly_week": 1,
"monthly_week_day": 2,
"end_times": 6,
"duration": 90
}
}'
Python
monthly_payload = {
"hub_id": hub_id,
"name": "Product Updates Webinar Series",
"timezone": "America/Los_Angeles",
"event_type": "RECURRING",
"access_level": "PRIVATE_UNRESTRICTED",
"meeting_type": "WEBINAR",
"attendance_type": "VIRTUAL",
"calendar": [
{
"start_time": "2025-02-04T22:00:00Z" # 2pm PT = 10pm UTC
}
],
"recurrence": {
"type": 3, # Monthly
"repeat_interval": 1, # Every month
"monthly_week": 1, # First week
"monthly_week_day": 2, # Tuesday
"end_times": 6, # 6 months
"duration": 90 # 90 minutes
}
}
response = requests.post(
"https://api.zoom.us/v2/zoom_events/events",
headers=headers,
json=monthly_payload
)
response.raise_for_status() # Check for HTTP errors
webinar_event_id = response.json()['event_id']
print(f"Monthly webinar series created: {webinar_event_id}")
JavaScript
async function createMonthlyWebinar(hubId) {
try {
const response = await axios.post(
"https://api.zoom.us/v2/zoom_events/events",
{
hub_id: hubId,
name: "Product Updates Webinar Series",
timezone: "America/Los_Angeles",
event_type: "RECURRING",
access_level: "PRIVATE_UNRESTRICTED",
meeting_type: "WEBINAR",
attendance_type: "VIRTUAL",
calendar: [
{
start_time: "2025-02-04T22:00:00Z",
},
],
recurrence: {
type: 3, // Monthly
repeat_interval: 1, // Every month
monthly_week: 1, // First week
monthly_week_day: 2, // Tuesday
end_times: 6, // 6 months
duration: 90, // 90 minutes
},
},
{
headers: {
"Content-Type": "application/json",
Authorization: "Bearer YOUR_ACCESS_TOKEN",
},
},
);
return response.data.event_id;
} catch (error) {
console.error(
"Failed to create monthly webinar:",
error.response?.data || error.message,
);
throw error;
}
}
This creates webinars on:
- Feb 4, 2025 (1st Tuesday)
- Mar 4, 2025 (1st Tuesday)
- Apr 1, 2025 (1st Tuesday)
- May 6, 2025 (1st Tuesday)
- Jun 3, 2025 (1st Tuesday)
- Jul 1, 2025 (1st Tuesday)
Scenario 3: Per-session registration
Sometimes attendees shouldn't register for all sessions—they should pick which specific occurrences to attend. This section shows you how to configure single_session registration for a training series.
Create training series with single-session registration
cURL
# First create the recurring event
curl -X POST 'https://api.zoom.us/v2/zoom_events/events' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-d '{
"hub_id": "4uzfv3JwTeyR5QpC3PXwMg",
"name": "Git & GitHub Training Series",
"timezone": "America/New_York",
"event_type": "RECURRING",
"access_level": "PRIVATE_UNRESTRICTED",
"meeting_type": "WEBINAR",
"attendance_type": "VIRTUAL",
"calendar": [
{
"start_time": "2025-01-08T18:00:00Z"
}
],
"recurrence": {
"type": 2,
"repeat_interval": 1,
"monthly_week_day": 3,
"end_times": 4,
"duration": 60
}
}'
# Create ticket type
curl -X POST 'https://api.zoom.us/v2/zoom_events/events/{eventId}/ticket_types' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-d '{
"name": "Per-Session Ticket",
"free": true,
"quantity": 100
}'
# Create access link with single_session registration
curl -X POST 'https://api.zoom.us/v2/zoom_events/events/{eventId}/access_links' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-d '{
"name": "Training Registration",
"type": "registration",
"is_default": true,
"ticket_type_id": "{ticket_type_id}",
"recurring_registration_option": "single_session",
"authentication_method": "bypass_auth"
}'
Python
# Create event
training_payload = {
"hub_id": hub_id,
"name": "Git & GitHub Training Series",
"timezone": "America/New_York",
"event_type": "RECURRING",
"access_level": "PRIVATE_UNRESTRICTED",
"meeting_type": "WEBINAR",
"attendance_type": "VIRTUAL",
"calendar": [
{
"start_time": "2025-01-08T18:00:00Z" # Wednesdays at 1pm ET
}
],
"recurrence": {
"type": 2,
"repeat_interval": 1,
"monthly_week_day": 3, # Wednesday
"end_times": 4,
"duration": 60
}
}
event_response = requests.post(
"https://api.zoom.us/v2/zoom_events/events",
headers=headers,
json=training_payload
)
event_response.raise_for_status() # Check for HTTP errors
training_event_id = event_response.json()['event_id']
# Create ticket type
ticket_response = requests.post(
f"https://api.zoom.us/v2/zoom_events/events/{training_event_id}/ticket_types",
headers=headers,
json={"name": "Per-Session Ticket", "type": "FREE", "inventory": 100}
)
ticket_response.raise_for_status() # Check for HTTP errors
ticket_type_id = ticket_response.json()['ticket_type_id']
# Create access link with single_session option
access_response = requests.post(
f"https://api.zoom.us/v2/zoom_events/events/{training_event_id}/access_links",
headers=headers,
json={
"name": "Training Registration",
"type": "registration",
"is_default": True,
"ticket_type_id": ticket_type_id,
"recurring_registration_option": "single_session",
"authentication_method": "bypass_auth"
}
)
access_response.raise_for_status() # Check for HTTP errors
print(f"Training series created with per-session registration")
JavaScript
async function createTrainingWithSingleSession(hubId) {
try {
// Create event
const eventResponse = await axios.post(
"https://api.zoom.us/v2/zoom_events/events",
{
hub_id: hubId,
name: "Git & GitHub Training Series",
timezone: "America/New_York",
event_type: "RECURRING",
access_level: "PRIVATE_UNRESTRICTED",
meeting_type: "WEBINAR",
attendance_type: "VIRTUAL",
calendar: [
{
start_time: "2025-01-08T18:00:00Z",
},
],
recurrence: {
type: 2,
repeat_interval: 1,
monthly_week_day: 3, // Wednesday
end_times: 4,
duration: 60,
},
},
{
headers: {
"Content-Type": "application/json",
Authorization: "Bearer YOUR_ACCESS_TOKEN",
},
},
);
const eventId = eventResponse.data.event_id;
// Create ticket type
const ticketResponse = await axios.post(
`https://api.zoom.us/v2/zoom_events/events/${eventId}/ticket_types`,
{ name: "Per-Session Ticket", free: true, quantity: 100 },
{
headers: {
"Content-Type": "application/json",
Authorization: "Bearer YOUR_ACCESS_TOKEN",
},
},
);
const ticketTypeId = ticketResponse.data.ticket_type_id;
// Create access link with single_session
await axios.post(
`https://api.zoom.us/v2/zoom_events/events/${eventId}/access_links`,
{
name: "Training Registration",
type: "registration",
is_default: true,
ticket_type_id: ticketTypeId,
recurring_registration_option: "single_session",
authentication_method: "bypass_auth",
},
{
headers: {
"Content-Type": "application/json",
Authorization: "Bearer YOUR_ACCESS_TOKEN",
},
},
);
return eventId;
} catch (error) {
console.error(
"Failed to create training series:",
error.response?.data || error.message,
);
throw error;
}
}
Managing attendee registrations
Register attendee for all sessions
When using recurring_registration_option: "all_sessions", simply omit session_ids in the ticket creation request.
cURL
curl -X POST 'https://api.zoom.us/v2/zoom_events/events/kNqLPC6hSFiZ9NpgjA549w/tickets' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-d '{
"tickets": [
{
"email": "jane.doe@company.com",
"first_name": "Jane",
"last_name": "Doe",
"ticket_type_id": "TICKET_TYPE_ID",
"send_notification": true,
"fast_join": false
}
]
}'
Python
def register_for_all_sessions(event_id, ticket_type_id, attendee_email, first_name, last_name):
"""Register attendee for entire series"""
tickets_url = f"https://api.zoom.us/v2/zoom_events/events/{event_id}/tickets"
payload = {
"tickets": [{
"email": attendee_email,
"first_name": first_name,
"last_name": last_name,
"ticket_type_id": ticket_type_id,
"send_notification": True,
"fast_join": False
}]
}
response = requests.post(tickets_url, headers=headers, json=payload)
response.raise_for_status() # Check for HTTP errors
ticket_data = response.json()
print(f"Registered {attendee_email} for all sessions")
return ticket_data['tickets'][0]['ticket_id']
# Example usage
ticket_id = register_for_all_sessions(
event_id,
ticket_type_id,
"jane.doe@company.com",
"Jane",
"Doe"
)
JavaScript
async function registerForAllSessions(
eventId,
ticketTypeId,
email,
firstName,
lastName,
) {
try {
const response = await axios.post(
`https://api.zoom.us/v2/zoom_events/events/${eventId}/tickets`,
{
tickets: [
{
email: email,
first_name: firstName,
last_name: lastName,
ticket_type_id: ticketTypeId,
send_notification: true,
fast_join: false,
},
],
},
{
headers: {
"Content-Type": "application/json",
Authorization: "Bearer YOUR_ACCESS_TOKEN",
},
},
);
console.log(`Registered ${email} for all sessions`);
return response.data.tickets[0].ticket_id;
} catch (error) {
console.error(
"Failed to register attendee:",
error.response?.data || error.message,
);
throw error;
}
}
Register attendee for specific sessions
When using recurring_registration_option: "single_session" or "multiple_sessions", include the specific session_ids array.
cURL
curl -X POST 'https://api.zoom.us/v2/zoom_events/events/kNqLPC6hSFiZ9NpgjA549w/tickets' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-d '{
"tickets": [
{
"email": "john.smith@company.com",
"first_name": "John",
"last_name": "Smith",
"ticket_type_id": "TICKET_TYPE_ID",
"session_ids": ["aVSyJswkTGC4o1gqVogTaA", "SPbxN1VwSrygHG0N6T2YtQ"],
"send_notification": true,
"fast_join": false
}
]
}'
Python
def register_for_specific_sessions(event_id, ticket_type_id, attendee_email,
first_name, last_name, session_ids):
"""Register attendee for selected sessions only"""
tickets_url = f"https://api.zoom.us/v2/zoom_events/events/{event_id}/tickets"
payload = {
"tickets": [{
"email": attendee_email,
"first_name": first_name,
"last_name": last_name,
"ticket_type_id": ticket_type_id,
"session_ids": session_ids, # Array of specific session IDs
"send_notification": True,
"fast_join": False
}]
}
response = requests.post(tickets_url, headers=headers, json=payload)
response.raise_for_status() # Check for HTTP errors
ticket_data = response.json()
print(f"Registered {attendee_email} for {len(session_ids)} sessions")
return ticket_data['tickets'][0]['ticket_id']
# Example: Register for weeks 1 and 2 only
session_ids_to_register = ["aVSyJswkTGC4o1gqVogTaA", "SPbxN1VwSrygHG0N6T2YtQ"]
ticket_id = register_for_specific_sessions(
event_id,
ticket_type_id,
"john.smith@company.com",
"John",
"Smith",
session_ids_to_register
)
JavaScript
async function registerForSpecificSessions(
eventId,
ticketTypeId,
email,
firstName,
lastName,
sessionIds,
) {
try {
const response = await axios.post(
`https://api.zoom.us/v2/zoom_events/events/${eventId}/tickets`,
{
tickets: [
{
email: email,
first_name: firstName,
last_name: lastName,
ticket_type_id: ticketTypeId,
session_ids: sessionIds, // Array of specific session IDs
send_notification: true,
fast_join: false,
},
],
},
{
headers: {
"Content-Type": "application/json",
Authorization: "Bearer YOUR_ACCESS_TOKEN",
},
},
);
console.log(`Registered ${email} for ${sessionIds.length} sessions`);
return response.data.tickets[0].ticket_id;
} catch (error) {
console.error(
"Failed to register attendee for specific sessions:",
error.response?.data || error.message,
);
throw error;
}
}
// Example: Register for first 3 training sessions
const sessionIds = [
"aVSyJswkTGC4o1gqVogTaA",
"SPbxN1VwSrygHG0N6T2YtQ",
"xYz123AbcDef",
];
await registerForSpecificSessions(
trainingEventId,
ticketTypeId,
"john.smith@company.com",
"John",
"Smith",
sessionIds,
);
Recurrence pattern reference
Weekly patterns
Every Monday
{
"type": 2,
"repeat_interval": 1,
"monthly_week_day": 1,
"end_times": 12,
"duration": 60
}
Every Tuesday and Thursday (not directly supported—use two events):
Weekly recurrence only supports one day per event. Create two separate recurring events for Tuesday and Thursday.
Bi-weekly (every 2 weeks) on Fridays
{
"type": 2,
"repeat_interval": 2,
"monthly_week_day": 5,
"end_times": 6,
"duration": 45
}
Monthly patterns
First Monday of each month
{
"type": 3,
"repeat_interval": 1,
"monthly_week": 1,
"monthly_week_day": 1,
"end_times": 12,
"duration": 90
}
Third Wednesday of each month
{
"type": 3,
"repeat_interval": 1,
"monthly_week": 3,
"monthly_week_day": 3,
"end_times": 6,
"duration": 120
}
Every 2 months (bi-monthly) on second Friday
{
"type": 3,
"repeat_interval": 2,
"monthly_week": 2,
"monthly_week_day": 5,
"end_times": 6,
"duration": 60
}
Daily patterns
Every weekday for 5 days
{
"type": 1,
"repeat_interval": 1,
"end_times": 5,
"duration": 30
}
Every day for 10 days
{
"type": 1,
"repeat_interval": 1,
"end_times": 10,
"duration": 45
}
Registration option comparison
| Option | Use Case | Behavior | Example |
|---|---|---|---|
all_sessions | Series attendance required | One ticket grants access to all occurrences | Team standup (attend all 12 weeks) |
single_session | Attendees pick one occurrence | Separate ticket per session | Drop-in training (attend week 2 only) |
multiple_sessions | Attendees select multiple specific sessions | One ticket for selected occurrences | Flexible series (attend weeks 1, 3, 5) |
Advanced management operations
Update a single occurrence
To modify one occurrence without affecting others, use the PATCH endpoint with the specific session_id.
Python Example
def update_single_occurrence(event_id, session_id, new_start_time):
"""Update start time for one occurrence"""
url = f"https://api.zoom.us/v2/zoom_events/events/{event_id}/sessions/{session_id}"
payload = {
"start_time": new_start_time
}
response = requests.patch(url, headers=headers, json=payload)
if response.status_code == 204:
print(f"Updated session {session_id}")
return response.status_code
# Move Week 3 meeting from Monday to Tuesday
update_single_occurrence(
event_id,
"xYz789SessionId",
"2025-01-21T15:00:00Z"
)
Cancel/Delete a Single Occurrence
Python Example
def cancel_single_occurrence(event_id, session_id):
"""Cancel one occurrence without deleting the entire series"""
url = f"https://api.zoom.us/v2/zoom_events/events/{event_id}/sessions/{session_id}"
response = requests.delete(url, headers=headers)
if response.status_code == 204:
print(f"Cancelled session {session_id}")
return response.status_code
# Cancel Week 4 due to holiday
cancel_single_occurrence(event_id, "cancelThisSessionId")
Verification checklist
After completing this tutorial, verify your recurring event:
- Event created with correct recurrence pattern (weekly/monthly/daily)
- Correct number of occurrences generated (check via GET /sessions)
- Ticket type created (even if free)
- Access link configured with appropriate
recurring_registration_option - Event published successfully
- Registration URL accessible
- Test registration completes for intended sessions
- Email notifications sent to registrants
Common error codes
| Code | HTTP | Meaning | Solution |
|---|---|---|---|
26201 | 403 | No permission on hub | Verify you have host role in the hub |
26202 | 400 | Schedule exceeds 6 days | This error is for multi-session events, not recurring. Use RECURRING event type with recurrence patterns |
26203 | 400 | end_time before start_time | Check your timezone conversions and time ordering |
26204 | 400 | Calendar missing times | Provide start_time in the request |
260207 | 400 | Hub unsupported for event type | Verify hub supports recurring events |
260200 | 403 | Event access denied | Check authentication and permissions |
2002 | 404 | Event not found | Verify event_id is correct |
26501 | 400 | Event not published | Publish event before creating tickets |
26502 | 400 | Invalid ticket type ID | Check ticket_type_id value |
FAQ
Can I have more than 60 occurrences?
No. The maximum is 60 occurrences per series.
Can occurrences run at the same time (concurrent sessions)?
No. Recurring events are sequential by design. All occurrences follow the recurrence pattern timeline. For concurrent sessions, use a Multi-Session (CONFERENCE) event instead.
Can I change the recurrence pattern after publishing?
No. The recurrence pattern is locked at creation. To change the pattern, you must delete the event and create a new one with the updated recurrence settings.
Results if I delete one occurrence?
Only that specific occurrence is cancelled. The remaining occurrences continue as scheduled. Registrants for that session receive cancellation notifications.
How do I handle holidays or exceptions?
Use the DELETE /events/{eventId}/sessions/{sessionId} endpoint to cancel individual occurrences. You cannot automatically skip holidays—manual deletion is required.
Can attendees switch which sessions they're registered for?
No.You cannot update an existing ticket with new session assignments. To change sessions, delete the ticket and create a new one using POST /events/{eventId}/tickets with the desired session_ids.
How are analytics reported for recurring events?
Analytics aggregate across all occurrences at the series level (total registrations, total attendance) with per-occurrence breakdowns available via session-specific reports.
What you have accomplished
You now know how to:
- Create recurring event series with weekly, monthly, and daily patterns.
- Configure registration options for series-level or per-occurrence attendance.
- List and verify all generated occurrences.
- Register attendees for entire series or specific sessions.
- Update or cancel individual occurrences without affecting the series.
- Handle common errors and edge cases.