Download, estimate, and safely delete Zoom cloud recordings for accounts with paid plans.
This script uses Zoom Server-to-Server OAuth. Create a
Server-to-Server OAuth app
in the Zoom App Marketplace, then copy
that app's credentials into config.py.
You do not paste a Zoom access token into this project. The script requests
short-lived access tokens automatically from Zoom by using the three app
credentials in config.py:
ACCOUNT_ID = R"your_zoom_account_id"
CLIENT_ID = R"your_zoom_client_id"
CLIENT_SECRET = R"your_zoom_client_secret"Find these values on the App Credentials page of your Zoom Server-to-Server OAuth app:
ACCOUNT_IDis Zoom's Account ID for the app.CLIENT_IDis the app's Client ID.CLIENT_SECRETis the app's Client Secret.
Keep config.py private. It is ignored by git because it contains secrets.
Scopes are not configured in config.py; add them to the Zoom app in the
Marketplace. If Zoom returns an authorization or permissions error, check the
app's scopes first, then confirm the three credential values above.
Configure these scopes in the Zoom Marketplace app before running the script.
For estimating and downloading recordings:
cloud_recording:read:list_user_recordings:admincloud_recording:read:list_recording_files:adminuser:read:list_users:adminifUSERSis empty and the script should scan every account user
For deletion modes, also add the corresponding cloud-recording delete/write scope in your Zoom app. If you use classic scopes, the older equivalents are:
recording:read:adminrecording:write:adminfor deletionuser:read:adminif scanning all users
-
Create and activate a Server-to-Server OAuth app in Zoom.
-
Clone or download this repository.
-
Copy
config_template.pytoconfig.py.cp config_template.py config.py
-
In
config.py, setACCOUNT_ID,CLIENT_ID, andCLIENT_SECRETfrom the Zoom app's App Credentials page. Then edit the date range, filters, andOUTPUT_PATH. -
Install Python 3, create a virtual environment, and install requirements.
python3 -m venv .venv . .venv/bin/activate python3 -m pip install -r requirements.txt -
Run a safe estimate first to confirm auth, scopes, filters, and disk-space reporting before downloading.
MODE = "estimate"
python3 zoom_batch_downloader.py
-
If the estimate looks right, switch to download mode and run again.
MODE = "download"
python3 zoom_batch_downloader.py
Set MODE in config.py.
MODE = "download"This is the default. The script scans matching recordings, builds an inventory,
checks the destination drive has enough free space for missing files plus
MINIMUM_FREE_DISK, then downloads.
If the destination is short on space and FAIL_IF_NOT_ENOUGH_SPACE = True, the
script exits before downloading anything. If set to False, it keeps the older
per-file wait behavior.
MODE = "estimate"Scans matching recordings and prints:
- matched meeting and file counts
- total matched remote size
- already-present local size
- additional download size needed
- destination free space and required free space
No files are downloaded.
MODE = "retry_not_ready"Retries meeting UUIDs logged in meetings.db, runs the same space preflight, and
downloads files that are now available. Successfully downloaded or already
present meetings are removed from the retry table.
MODE = "delete_bulk"
DRY_RUN = True
DELETE_ACTION = "trash"
DELETE_SCOPE = "files"Bulk deletion uses the same filters as download mode. It is a dry run by default and prints the exact targets without calling Zoom delete endpoints.
Use DELETE_SCOPE = "files" to delete matching recording files individually.
Use DELETE_SCOPE = "meetings" to delete each matched meeting's full recording
set.
To perform a real trash delete:
MODE = "delete_bulk"
DRY_RUN = False
DELETE_ACTION = "trash"
DELETE_SCOPE = "files"
CONFIRM_DELETE = "DELETE"Delete a single recording file:
MODE = "delete_one"
DRY_RUN = True
DELETE_ACTION = "trash"
DELETE_MEETING_UUID = "MEETING_UUID"
DELETE_RECORDING_ID = "RECORDING_FILE_ID"Delete a meeting's full recording set by leaving DELETE_RECORDING_ID empty:
MODE = "delete_one"
DRY_RUN = True
DELETE_ACTION = "trash"
DELETE_MEETING_UUID = "MEETING_UUID"
DELETE_RECORDING_ID = NoneTo perform the real trash delete, set:
DRY_RUN = False
CONFIRM_DELETE = "DELETE"Permanent deletion is intentionally hard to trigger. To permanently delete recordings from Zoom instead of moving them to trash, all three settings are required:
DELETE_ACTION = "delete"
ALLOW_PERMANENT_DELETE = True
CONFIRM_DELETE = "DELETE PERMANENTLY"Permanent deletion cannot be undone through Zoom trash recovery.
- Delete modes never delete local downloaded files.
- Delete modes are dry-run by default.
- Trash deletion requires explicit confirmation.
- Permanent deletion requires separate explicit confirmation.
- Downloads keep TLS verification enabled and use bearer-token headers rather than appending tokens to logged URLs.
- Error output redacts bearer tokens and token query parameters.
Code written by Georg Kasmin, Lane Campbell, Sami Hassan and Aness Zurba.