Developing what I thought would be a simple tool to retrieve the list of active participants in a Zoom meeting.
I’ve reviewed all the related topics I could find. It seems that many folks have tried and probably failed to achieve this goal. Instability, long delays, and complex rules about whether an email address is ever returned at all.
The list of issues is long and goes back quite a long way in the history of this API.
What has become really clear is that what should be a simple call produces results that are unusable.
- Records are probably out-of-date
- The meaning of join_time and leave_time across multiple records for the same user are either ambiguous or actually incorrect.
- The meaning of status (in_waiting_room | in_meeting) doesn’t match reality. Not even after resampling the query over several hours.
- The user_name varies depending upon an unknown set of criteria. Returning the account name, or the participant name within the meeting.
I’ve been working on analysing the results for several hours as the meeting participants moved in and out of breakout rooms. I left my own account logged in for the duration without moving into breakout rooms.
My own record in the output never changed from this:
in_waiting_room 2025-08-15T23:45:37Z 2025-08-15T23:46:30Z My Name
Here is the basic code sample I’m using so there is no ambiguity about which API being used and how it is being called.
ZOOM_PARTICIPANTS_URL = “https://api.zoom.us/v2/metrics/meetings/{}/participants”
def get_access_token(client_id, client_secret, account_id):
headers = {
“Authorization”: “Basic " + base64.b64encode(f”{client_id}:{client_secret}".encode()).decode()
}
data = {
“grant_type”: “account_credentials”,
“account_id”: account_id
}
response = requests.post(ZOOM_TOKEN_URL, headers=headers, data=data)
response.raise_for_status()
return response.json()[“access_token”]
def get_all_participants(meeting_id, access_token):
participants =
seen = set()
page_size = 150
next_page_token = None
headers = {
“Authorization”: f"Bearer {access_token}"
}
while True:
params = {
"type": "live",
"page_size": page_size,
}
if next_page_token:
params["next_page_token"] = next_page_token
url = ZOOM_PARTICIPANTS_URL.format(meeting_id)
response = requests.get(url, headers=headers, params=params)
if response.status_code != 200:
handle_http_error(response)
data = response.json()
for p in data.get("participants", []):
# Create a unique key based on fields that identify a participant
key = (p.get("participant_user_id"), p.get("email"), p.get("user_name"))
if key not in seen:
seen.add(key)
participants.append(p)
next_page_token = data.get("next_page_token")
if not next_page_token:
break
return participants