# 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 ```shell curl -X GET 'https://api.zoom.us/v2/zoom_events/hubs?role_type=host' \ -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' ``` ### Python ```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) ```javascript 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 ```json { "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 ```shell 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 ```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) ```javascript 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 ```json { "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 ```shell 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 ```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 ```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 ```shell 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 ```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 ```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 ```shell 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 ```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 ```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 ```shell curl -X GET 'https://api.zoom.us/v2/zoom_events/events/kNqLPC6hSFiZ9NpgjA549w/sessions' \ -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' ``` ### Python ```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 ```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 ```json { "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 ```shell 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 ```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 ```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 ```shell # 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 ```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 ```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 ```shell 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 ```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 ```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 ```shell 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 ```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 ```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** ```json { "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** ```json { "type": 2, "repeat_interval": 2, "monthly_week_day": 5, "end_times": 6, "duration": 45 } ``` --- ### Monthly patterns **First Monday of each month** ```json { "type": 3, "repeat_interval": 1, "monthly_week": 1, "monthly_week_day": 1, "end_times": 12, "duration": 90 } ``` **Third Wednesday of each month** ```json { "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** ```json { "type": 3, "repeat_interval": 2, "monthly_week": 2, "monthly_week_day": 5, "end_times": 6, "duration": 60 } ``` --- ### Daily patterns **Every weekday for 5 days** ```json { "type": 1, "repeat_interval": 1, "end_times": 5, "duration": 30 } ``` **Every day for 10 days** ```json { "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 ```python 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 ```python 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.