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:

  1. Create recurring event series with daily, weekly, or monthly patterns.
  2. Configure different registration options (series-level vs per-occurrence).
  3. List and manage individual occurrences.
  4. 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:event and zoom_events:read:list_sessions scopes
  • 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 recurrence
  • repeat_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 occurrences
  • duration: 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 recurrence
  • repeat_interval: 1 = Every 1 month
  • monthly_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 occurrences
  • duration: 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

OptionUse CaseBehaviorExample
all_sessionsSeries attendance requiredOne ticket grants access to all occurrencesTeam standup (attend all 12 weeks)
single_sessionAttendees pick one occurrenceSeparate ticket per sessionDrop-in training (attend week 2 only)
multiple_sessionsAttendees select multiple specific sessionsOne ticket for selected occurrencesFlexible 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

CodeHTTPMeaningSolution
26201403No permission on hubVerify you have host role in the hub
26202400Schedule exceeds 6 daysThis error is for multi-session events, not recurring. Use RECURRING event type with recurrence patterns
26203400end_time before start_timeCheck your timezone conversions and time ordering
26204400Calendar missing timesProvide start_time in the request
260207400Hub unsupported for event typeVerify hub supports recurring events
260200403Event access deniedCheck authentication and permissions
2002404Event not foundVerify event_id is correct
26501400Event not publishedPublish event before creating tickets
26502400Invalid ticket type IDCheck 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.