раздел 03

Скачать файл на сервер

У вас есть download_url нужного файла из прошлого раздела. Сама по себе ссылка не отдаст файл - она требует авторизации. Есть два документированных способа передать токен.

Два способа авторизации

  • Параметр в ссылке. Добавить к download_url query-параметр access_token с вашим токеном.
  • Заголовок. Отправить заголовок Authorization: Bearer ACCESS_TOKEN при запросе на download_url.

Оба варианта рабочие, выбирайте удобный. По ссылке отдаётся настоящий медиафайл (mp4, m4a и т.д.), но через редирект - поэтому клиент должен идти за редиректами.

curl

Способ через заголовок. Флаг -L обязателен - он велит curl идти за редиректами до реального файла. -o задаёт имя файла на сервере.

curl -L -H "Authorization: Bearer ACCESS_TOKEN" \
  "https://api.zoom.us/v2/recording/download/FILE_PATH" \
  -o "2026-05-14-weekly-call.mp4"

Способ через параметр в ссылке - то же самое, но токен в URL:

curl -L \
  "https://api.zoom.us/v2/recording/download/FILE_PATH?access_token=ACCESS_TOKEN" \
  -o "2026-05-14-weekly-call.mp4"

Python

Для больших видео не держите файл целиком в памяти: качайте потоком и пишите по чанкам. У requests для этого есть stream=True и iter_content.

import requests


def download_recording(download_url: str, token: str, out_path: str):
    headers = {"Authorization": f"Bearer {token}"}
    with requests.get(download_url, headers=headers, stream=True, allow_redirects=True) as resp:
        resp.raise_for_status()
        with open(out_path, "wb") as f:
            for chunk in resp.iter_content(chunk_size=1024 * 1024):
                if chunk:
                    f.write(chunk)
    return out_path

allow_redirects=True у requests стоит по умолчанию, но для записей Zoom оно критично - оставляйте включённым.

Имя файла

Чтобы потом находить записи, собирайте имя из данных встречи: дата плюс тема. Тему придётся почистить от символов, недопустимых в имени файла.

import re
from datetime import datetime


def make_filename(meeting: dict, file: dict) -> str:
    start = meeting.get("start_time", "")
    date = start[:10] if start else datetime.now().strftime("%Y-%m-%d")
    topic = re.sub(r"[^\w\- ]", "", meeting.get("topic", "meeting")).strip()
    topic = re.sub(r"\s+", "-", topic).lower()
    ext = file.get("file_extension", "mp4").lower()
    return f"{date}-{topic}.{ext}"

Проверка, что скачали целиком

В объекте файла есть file_size в байтах. После скачивания сравните его с размером файла на диске - так вы поймаете обрыв загрузки до того, как удалите оригинал из Zoom.

import os

expected = file["file_size"]
actual = os.path.getsize(out_path)
if actual != expected:
    raise RuntimeError(f"Размер не совпал: ждали {expected}, получили {actual}")

Дальше - куда складывать файлы и как сделать так, чтобы они скачивались сами.