Авторизация Zoom OAuth: потоки и токены
Справочник по авторизации в Zoom: выбор потока OAuth, области доступа, срок жизни и обновление токенов, коды ошибок.
- Что делает
- Справочник по авторизации в Zoom: выбор потока OAuth, области доступа, срок жизни и обновление токенов, коды ошибок.
- Когда брать
- Когда нужно выбрать тип OAuth-доступа для приложения Zoom, настроить области доступа, обновление токенов или разобрать ошибки авторизации.
- Когда не брать
- Если авторизация не нужна или проблема не связана с OAuth (общая настройка есть в setup-zoom-oauth).
- Пример запроса
- Какой поток OAuth выбрать для серверного скрипта, который скачивает записи наших встреч Zoom?
- Нужно подключить
- терминал
Входит в плагин zoom-plugin. В Cowork и Claude Code можно поставить плагин целиком.
Как включить
- Скачайте архив и распакуйте его.
- Положите папку
zoom-oauthв~/.claude/skills/. - Откройте Claude Code и опишите задачу своими словами: Claude подхватит скилл по описанию.
Текст
---
name: zoom-oauth
description: Справочный скилл по авторизации в Zoom. Используй после выбора процесса авторизации при выборе учётных данных приложения, типов доступа (grant), областей доступа (scopes), поведения при обновлении токенов или при отладке сбоев Zoom OAuth.
user-invocable: false
triggers:
- zoom oauth
- zoom authentication
- zoom authorization
- server to server oauth
- s2s oauth
- zoom access token
- zoom refresh token
- authorization code flow
- device authorization
- pkce
- zoom api authentication
- oauth error 4709
- oauth error 4733
- oauth error 4735
- redirect uri mismatch
---
Zoom OAuth
Справочный материал по авторизации в Zoom и жизненному циклу токенов. Сначала предпочитай setup-zoom-oauth, а затем используй этот скилл за точными сведениями о потоке, областях доступа и ошибках.
Zoom OAuth
Аутентификация и авторизация для API Zoom.
📖 Полная документация
Подробные руководства, шаблоны для продакшена и устранение неполадок — в разделе «Сводный указатель» ниже.
Быстрая навигация:
- [Пятиминутный чек-лист](RUNBOOK.md) — предварительные проверки перед глубокой отладкой
- [Потоки OAuth](concepts/oauth-flows.md) — какой поток выбрать и как работает каждый
- [Жизненный цикл токена](concepts/token-lifecycle.md) — срок действия, обновление и отзыв
- [Примеры для продакшена](examples/s2s-oauth-redis.md) — кэширование в Redis, хранение в MySQL, автоматическое обновление
- [Устранение неполадок](troubleshooting/common-errors.md) — коды ошибок 4700–4741
Предварительные требования
- Приложение Zoom, созданное в Marketplace
- Client ID и Client Secret
- Для S2S OAuth: Account ID
Четыре сценария авторизации
| Сценарий | Тип приложения | Тип доступа (Grant Type) | Отраслевое название |
|---|---|---|---|
| Авторизация аккаунта | Server-to-Server | account_credentials | Client Credentials Grant, M2M, Two-legged OAuth |
| Авторизация пользователя | General | authorization_code | Authorization Code Grant, Three-legged OAuth |
| Авторизация устройства | General | urn:ietf:params:oauth:grant-type:device_code | Device Authorization Grant (RFC 8628) |
| Авторизация клиента | General | client_credentials | Client Credentials Grant (для чат-бота) |
Отраслевая терминология
| Термин | Значение |
|---|---|
| Two-legged OAuth | Пользователь не участвует (клиент ↔ сервер) |
| Three-legged OAuth | Пользователь участвует (пользователь ↔ клиент ↔ сервер) |
| M2M | Machine-to-Machine, «машина — машина» (серверные службы) |
| Публичный клиент (Public client) | Не может хранить секреты (мобильное приложение, одностраничное приложение) → используй PKCE |
| Конфиденциальный клиент (Confidential client) | Может хранить секреты (серверные бэкенды) |
| PKCE | Proof Key for Code Exchange (RFC 7636), произносится «пикси» |
Какой поток выбрать?
┌─────────────────────┐
│ Что вы создаёте? │
│ │
└──────────┬──────────┘
│
┌────────────────────┼────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Серверная │ │ Приложение для │ │ Только чат-бот │
│ автоматизация │ │ других людей │ │ (Team Chat) │
│ (ваш аккаунт) │ │ и компаний │ │ │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
▼ │ ▼
┌─────────────────┐ │ ┌─────────────────┐
│ АККАУНТ │ │ │ КЛИЕНТ │
│ (S2S OAuth) │ │ │ (чат-бот) │
└─────────────────┘ │ └─────────────────┘
│
▼
┌─────────────────────┐
│ Есть ли у │
│ устройства браузер?│
└──────────┬──────────┘
│
┌───────────────┴───────────────┐
│ НЕТ ДА│
▼ ▼
┌─────────────────────────┐ ┌─────────────────┐
│ УСТРОЙСТВО │ │ ПОЛЬЗОВАТЕЛЬ │
│ (Device Flow) │ │ (Auth Code) │
│ │ │ │
│ Примеры: │ │ + PKCE, если │
│ • Смарт-ТВ │ │ клиент │
│ • Устройство Meeting SDK│ │ публичный │
└─────────────────────────┘ └─────────────────┘
Авторизация аккаунта (Server-to-Server OAuth)
Для серверной автоматизации без участия пользователя.
Запрос токена доступа
POST https://zoom.us/oauth/token?grant_type=account_credentials&account_id={ACCOUNT_ID}
Headers:
Authorization: Basic {Base64(ClientID:ClientSecret)}
Ответ
{
"access_token": "eyJ...",
"token_type": "bearer",
"expires_in": 3600,
"scope": "user:read:user:admin",
"api_url": "https://api.zoom.us"
}
Обновление
Токены доступа действуют 1 час. Отдельного потока обновления нет — просто запроси новый токен.
Авторизация пользователя (поток с кодом авторизации)
Для приложений, которые действуют от имени пользователей.
Шаг 1: перенаправь пользователя на авторизацию
https://zoom.us/oauth/authorize?response_type=code&client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}
Используй https://zoom.us/oauth/authorize для получения согласия, а https://zoom.us/oauth/token — для обмена на токен.
Необязательные параметры:
| Параметр | Описание |
|---|---|
state | Защита от CSRF, сохраняет состояние на протяжении потока |
code_challenge | Для PKCE (см. ниже) |
code_challenge_method | S256 или plain (по умолчанию: plain) |
Шаг 2: пользователь даёт разрешение
- Пользователь входит в аккаунт и даёт разрешение
- Происходит перенаправление на
redirect_uriс кодом авторизации: ``https://example.com/?code={AUTHORIZATION_CODE}``
Шаг 3: обмен кода на токен
POST https://zoom.us/oauth/token?grant_type=authorization_code&code={CODE}&redirect_uri={REDIRECT_URI}
Headers:
Authorization: Basic {Base64(ClientID:ClientSecret)}
С PKCE: добавь параметр code_verifier.
Ответ
{
"access_token": "eyJ...",
"token_type": "bearer",
"refresh_token": "eyJ...",
"expires_in": 3600,
"scope": "user:read:user",
"api_url": "https://api.zoom.us"
}
Обновляющий токен (Refresh Token)
POST https://zoom.us/oauth/token?grant_type=refresh_token&refresh_token={REFRESH_TOKEN}
Headers:
Authorization: Basic {Base64(ClientID:ClientSecret)}
- Токены доступа действуют 1 час
- Срок жизни refresh-токена может различаться; для некоторых пользовательских потоков обычно около 90 дней. Относись к этому как к настройке или поведению, которое может измениться, и полагайся на ошибки во время выполнения и повторную авторизацию как запасной вариант.
- Всегда используй для следующего запроса самый свежий refresh-токен
- Если refresh-токен истёк, перенаправь пользователя на URL авторизации, чтобы начать поток заново
Приложения уровня пользователя и уровня аккаунта
| Тип | Кто может дать разрешение | Доступ в рамках областей |
|---|---|---|
| Уровня пользователя | Любой отдельный пользователь | Только в пределах его собственных данных |
| Уровня аккаунта | Пользователь с правами администратора | Доступ ко всему аккаунту (административные области доступа) |
Авторизация устройства (Device Flow)
Для устройств без браузера (например, приложения на Meeting SDK).
Предварительные требования
Включи «Use App on Device» в: Features > Embed > Enable Meeting SDK
Шаг 1: запрос кода устройства
POST https://zoom.us/oauth/devicecode?client_id={CLIENT_ID}
Headers:
Authorization: Basic {Base64(ClientID:ClientSecret)}
Ответ
{
"device_code": "DEVICE_CODE",
"user_code": "abcd1234",
"verification_uri": "https://zoom.us/oauth_device",
"verification_uri_complete": "https://zoom.us/oauth/device/complete/{CODE}",
"expires_in": 900,
"interval": 5
}
Шаг 2: авторизация пользователя
Направь пользователя:
- на
verification_uriи покажиuser_codeдля ручного ввода, ИЛИ - на
verification_uri_complete(код пользователя уже подставлен)
Пользователь входит в аккаунт и разрешает доступ приложению.
Шаг 3: опрашивай получение токена
Опрашивай с интервалом interval (5 секунд), пока пользователь не даст разрешение:
POST https://zoom.us/oauth/token?grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code={DEVICE_CODE}
Headers:
Authorization: Basic {Base64(ClientID:ClientSecret)}
Ответ
{
"access_token": "eyJ...",
"token_type": "bearer",
"refresh_token": "eyJ...",
"expires_in": 3599,
"scope": "user:read:user user:read:token",
"api_url": "https://api.zoom.us"
}
Ответы при опросе
| Ответ | Значение | Действие |
|---|---|---|
| Возвращён токен | Пользователь дал разрешение | Сохрани токены, готово |
error: authorization_pending | Пользователь ещё не дал разрешение | Продолжай опрос с тем же интервалом |
error: slow_down | Слишком частый опрос | Увеличь интервал на 5 секунд |
error: expired_token | Код устройства истёк (15 мин) | Начни поток заново с шага 1 |
error: access_denied | Пользователь отказал в авторизации | Обработай отказ, не повторяй попытку |
Реализация опроса
async function pollForToken(deviceCode, interval) {
while (true) {
await sleep(interval * 1000);
try {
const response = await axios.post(
`https://zoom.us/oauth/token?grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code=${deviceCode}`,
null,
{ headers: { 'Authorization': `Basic ${credentials}` } }
);
return response.data; // Success - got tokens
} catch (error) {
const err = error.response?.data?.error;
if (err === 'authorization_pending') continue;
if (err === 'slow_down') { interval += 5; continue; }
throw error; // expired_token or access_denied
}
}
}
Обновление
Так же, как при авторизации пользователя. Если refresh-токен истёк, запусти поток устройства заново с шага 1.
Авторизация клиента (чат-бот)
Только для операций чат-бота с сообщениями.
Запрос токена
POST https://zoom.us/oauth/token?grant_type=client_credentials
Headers:
Authorization: Basic {Base64(ClientID:ClientSecret)}
Ответ
{
"access_token": "eyJ...",
"token_type": "bearer",
"expires_in": 3600,
"scope": "imchat:bot",
"api_url": "https://api.zoom.us"
}
Обновление
Токены действуют 1 час. Потока обновления нет — просто запроси новый токен.
Использование токенов доступа
Вызов API
GET https://api.zoom.us/v2/users/me
Headers:
Authorization: Bearer {ACCESS_TOKEN}
Контекст «me»
Замени userID на me, чтобы обращаться к пользователю, связанному с токеном:
| Конечная точка | Методы |
|---|---|
/v2/users/me | GET, PATCH |
/v2/users/me/token | GET |
/v2/users/me/meetings | GET, POST |
Отзыв токена доступа
Работает для всех типов авторизации.
POST https://zoom.us/oauth/revoke?token={ACCESS_TOKEN}
Headers:
Authorization: Basic {Base64(ClientID:ClientSecret)}
Ответ
{
"status": "success"
}
PKCE (Proof Key for Code Exchange)
Для публичных клиентов, которые не могут надёжно хранить секреты (мобильные, одностраничные и настольные приложения).
Когда использовать PKCE
| Тип клиента | Нужен ли PKCE? | Почему |
|---|---|---|
| Мобильное приложение | Да | Не может надёжно хранить client secret |
| Одностраничное приложение (SPA) | Да | JavaScript виден пользователям |
| Настольное приложение | Да | Бинарный файл можно декомпилировать |
| Meeting SDK (на стороне клиента) | Да | Работает на устройстве пользователя |
| Серверный бэкенд | Необязательно | Может хранить секреты, но PKCE добавляет безопасности |
Как работает PKCE
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Клиент │ │ Zoom │ │ Zoom │
│приложение│ │ Auth │ │ Token │
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
│ 1. Создать случайный code_verifier │ │
│ 2. Вычислить code_challenge = SHA256(verifier) │
│ │ │
│ ─────── /authorize + code_challenge ──► │ │
│ │ │
│ ◄────── authorization_code ──────────── │ │
│ │ │
│ ─────────────── /token + code_verifier ─┼────────────────────────────► │
│ │ │
│ │ Проверка: SHA256(verifier) │
│ │ == challenge │
│ │ │
│ ◄───────────────────────────────────────┼─────── access_token ──────── │
│ │ │
Реализация (Node.js)
const crypto = require('crypto');
function generatePKCE() {
const verifier = crypto.randomBytes(32).toString('base64url');
const challenge = crypto.createHash('sha256').update(verifier).digest('base64url');
return { verifier, challenge };
}
const pkce = generatePKCE();
const authUrl = `https://zoom.us/oauth/authorize?` +
`response_type=code&` +
`client_id=${CLIENT_ID}&` +
`redirect_uri=${REDIRECT_URI}&` +
`code_challenge=${pkce.challenge}&` +
`code_challenge_method=S256`;
// Store pkce.verifier in session for callback
Обмен на токен с PKCE
POST https://zoom.us/oauth/token?grant_type=authorization_code&code={CODE}&redirect_uri={REDIRECT_URI}&code_verifier={VERIFIER}
Headers:
Authorization: Basic {Base64(ClientID:ClientSecret)}
Отзыв авторизации (Deauthorization)
Когда пользователь удаляет ваше приложение, Zoom отправляет вебхук на URL конечной точки уведомлений об отзыве авторизации (Deauthorization Notification Endpoint URL).
Событие вебхука
{
"event": "app_deauthorized",
"event_ts": 1740439732278,
"payload": {
"account_id": "ACCOUNT_ID",
"user_id": "USER_ID",
"signature": "SIGNATURE",
"deauthorization_time": "2019-06-17T13:52:28.632Z",
"client_id": "CLIENT_ID"
}
}
Требования
- Удали все связанные данные пользователя после получения этого события
- Проверяй подпись вебхука (используй секретный токен; токен проверки (verification token) признан устаревшим в октябре 2023 года)
- Вебхуки об отзыве авторизации получают только публичные приложения (не приватные и не тестовые)
Процесс предварительного одобрения (Pre-Approval Flow)
Некоторые аккаунты Zoom требуют, чтобы администратор Marketplace заранее одобрил приложения, прежде чем пользователи смогут их авторизовать.
- Пользователи могут запросить предварительное одобрение у своего администратора
- Приложениям уровня аккаунта (административные области доступа) нужны соответствующие права роли
Active Apps Notifier (AAN)
Функция во время встречи, которая показывает приложения с доступом к содержимому в реальном времени.
- Показывает значок и подсказку со сведениями о приложении, типе содержимого, к которому оно обращается, и об одобряющем аккаунте
- Поддерживается: клиент Zoom 5.6.7+, Meeting SDK 5.9.0+
Области доступа OAuth (OAuth Scopes)
Типы областей доступа
| Тип | Описание | Для чего |
|---|---|---|
| Классические области доступа (Classic scopes) | Прежние области доступа (уровни user, admin, master) | Существующие приложения |
| Детальные области доступа (Granular scopes) | Новые мелкие области доступа с поддержкой необязательных | Новые приложения |
Классические области доступа
Для ранее созданных приложений. Три уровня:
- Уровень пользователя (User-level): доступ к данным отдельного пользователя
- Уровень администратора (Admin-level): доступ ко всему аккаунту, нужна роль администратора
- Главный уровень (Master-level): для схем с главным и дочерними аккаунтами, нужен владелец аккаунта
Полный список: https://developers.zoom.us/docs/integrations/oauth-scopes/
Детальные области доступа
Для новых приложений. Формат: <service>:<action>:<data_claim>:<access>
| Компонент | Значения |
|---|---|
| service | meeting, webinar, user, recording и др. |
| action | read, write, update, delete |
| data_claim | Категория данных (например, participants, settings) |
| access | пусто (пользователь), admin, master |
Пример: meeting:read:list_meetings:admin
Полный список: https://developers.zoom.us/docs/integrations/oauth-scopes-granular/
Необязательные области доступа
Детальные области доступа можно пометить как необязательные — пользователи сами решают, предоставлять ли их.
Базовая авторизация (используются значения по умолчанию из потока сборки):
https://zoom.us/oauth/authorize?response_type=code&client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}
Расширенная авторизация (свои области доступа для каждого запроса):
https://zoom.us/oauth/authorize?client_id={CLIENT_ID}&response_type=code&redirect_uri={REDIRECT_URI}&scope={required_scopes}&optional_scope={optional_scopes}
Включить ранее предоставленные области доступа:
https://zoom.us/oauth/authorize?...&include_granted_scopes&scope={additional_scopes}
Миграция с классических на детальные области доступа
- Manage > выбери приложение > edit
- Страница Scope > вкладка Development > нажми Migrate
- Проверь автоматически назначенные детальные области доступа, удали лишние, отметь необязательные
- Протестируй
- Вкладка Production > нажми Migrate
Примечания:
- Проверка не нужна, если ты только переносишь или сокращаешь области доступа
- Существующие токены пользователей продолжают работать со значениями классических областей доступа до повторной авторизации
- Новые пользователи после миграции получают детальные области доступа
Частые коды ошибок
| Код | Сообщение | Решение |
|---|---|---|
| 4700 | Token cannot be empty | Проверь, что в заголовке Authorization есть действительный токен |
| 4702/4704 | Invalid client | Проверь Client ID и Client Secret |
| 4705 | Grant type not supported | Используй: account_credentials, authorization_code, urn:ietf:params:oauth:grant-type:device_code или client_credentials |
| 4706 | Client ID or secret missing | Добавь учётные данные в заголовок или параметры запроса |
| 4709 | Redirect URI mismatch | Убедись, что redirect_uri точно совпадает с настройкой приложения (включая завершающий слэш) |
| 4711 | Refresh token invalid | Области доступа токена не совпадают с областями доступа клиента |
| 4717 | App has been disabled | Обратись в поддержку Zoom |
| 4733 | Code is expired | Коды авторизации действуют 5 минут — начни поток заново |
| 4734 | Invalid authorization code | Заново получи код авторизации |
| 4735 | Owner of token does not exist | Пользователь удалён из аккаунта — пройди авторизацию заново |
| 4741 | Token has been revoked | Используй самый свежий токен из последней авторизации |
Полный список ошибок — в references/oauth-errors.md.
Краткий справочник
| Поток | Тип доступа (Grant Type) | Срок действия токена | Обновление |
|---|---|---|---|
| Аккаунт (S2S) | account_credentials | 1 час | Запросить новый токен |
| Пользователь | authorization_code | 1 час | Использовать refresh_token (срок действия 90 дней) |
| Устройство | urn:ietf:params:oauth:grant-type:device_code | 1 час | Использовать refresh_token (срок действия 90 дней) |
| Клиент (чат-бот) | client_credentials | 1 час | Запросить новый токен |
Рекомендации по демонстрационным приложениям
Если ты создаёшь демонстрационное приложение с OAuth, указывай его базовый URL времени выполнения в README этого демо-проекта или в .env.example, а не в этом общем скилле.
Ресурсы
- Документация OAuth: https://developers.zoom.us/docs/integrations/oauth/
- Документация S2S OAuth: https://developers.zoom.us/docs/internal-apps/s2s-oauth/
- Статья о PKCE: https://developers.zoom.us/blog/pcke-oauth-with-postman-rest-api/
- Классические области доступа: https://developers.zoom.us/docs/integrations/oauth-scopes/
- Детальные области доступа: https://developers.zoom.us/docs/integrations/oauth-scopes-granular/
Сводный указатель
_Этот раздел перенесён из SKILL.md._
Путь быстрого старта
Если ты впервые работаешь с Zoom OAuth, иди в таком порядке:
- Сначала выполни предварительные проверки → [RUNBOOK.md](RUNBOOK.md)
- Выбери поток OAuth → [concepts/oauth-flows.md](concepts/oauth-flows.md)
- 4 потока: S2S (бэкенд), пользовательский (SaaS), Device (нет браузера), чат-бот
- Матрица выбора: какой поток подходит для твоего сценария?
- Пойми жизненный цикл токена → [concepts/token-lifecycle.md](concepts/token-lifecycle.md)
- КРИТИЧНО: как токены истекают, обновляются и отзываются
- Частые ловушки: ротация refresh-токена
- Реализуй свой поток → переходи к примерам:
- Серверная автоматизация → [examples/s2s-oauth-redis.md](examples/s2s-oauth-redis.md)
- SaaS-приложение → [examples/user-oauth-mysql.md](examples/user-oauth-mysql.md)
- Мобильное или одностраничное приложение → [examples/pkce-implementation.md](examples/pkce-implementation.md)
- Устройство (ТВ, киоск) → [examples/device-flow.md](examples/device-flow.md)
- Исправь проблемы с redirect URI → [troubleshooting/redirect-uri-issues.md](troubleshooting/redirect-uri-issues.md)
- Самая частая ошибка OAuth: несовпадение redirect URI
- Реализуй обновление токена → [examples/token-refresh.md](examples/token-refresh.md)
- Шаблон автоматического промежуточного слоя
- Обработка ротации refresh-токена
- Устрани ошибки → [troubleshooting/common-errors.md](troubleshooting/common-errors.md)
- Таблицы кодов ошибок (диапазон 4700–4741)
- Быстрый порядок диагностики
Структура документации
oauth/
├── SKILL.md # Обзор основного скилла
├── SKILL.md # Этот файл — навигационное руководство
│
├── concepts/ # Основные концепции OAuth
│ ├── oauth-flows.md # 4 потока: S2S, пользовательский, Device, чат-бот
│ ├── token-lifecycle.md # Срок действия, обновление, отзыв
│ ├── pkce.md # Защита PKCE для публичных клиентов
│ ├── scopes-architecture.md # Классические и детальные области доступа
│ └── state-parameter.md # Защита от CSRF с помощью state
│
├── examples/ # Полные рабочие примеры кода
│ ├── s2s-oauth-basic.md # Минимальный пример S2S OAuth
│ ├── s2s-oauth-redis.md # S2S OAuth с кэшированием в Redis (продакшен)
│ ├── user-oauth-basic.md # Минимальный пример пользовательского OAuth
│ ├── user-oauth-mysql.md # Пользовательский OAuth с MySQL и шифрованием (продакшен)
│ ├── device-flow.md # Поток авторизации устройства
│ ├── pkce-implementation.md # PKCE для одностраничных и мобильных приложений
│ └── token-refresh.md # Промежуточный слой автоматического обновления токенов
│
├── troubleshooting/ # Руководства по устранению проблем
│ ├── common-errors.md # Коды ошибок 4700-4741
│ ├── redirect-uri-issues.md # Самая частая ошибка OAuth
│ ├── token-issues.md # Просроченные, отозванные и недействительные токены
│ └── scope-issues.md # Ошибки несовпадения областей доступа
│
└── references/ # Справочная документация
├── oauth-errors.md # Полный справочник кодов ошибок
├── classic-scopes.md # Справочник по классическим областям доступа
└── granular-scopes.md # Справочник по детальным областям доступа
По сценариям использования
Хочу автоматизировать задачи Zoom в собственном аккаунте
- [Потоки OAuth](concepts/oauth-flows.md#server-to-server-s2s-oauth) — объяснение S2S OAuth
- [S2S OAuth с Redis](examples/s2s-oauth-redis.md) — шаблон для продакшена с кэшированием в Redis
- [Жизненный цикл токена](concepts/token-lifecycle.md) — токен на 1 час, без обновления
Хочу создать SaaS-приложение для других пользователей Zoom
- [Потоки OAuth](concepts/oauth-flows.md#user-authorization-oauth) — объяснение пользовательского OAuth
- [Пользовательский OAuth с MySQL](examples/user-oauth-mysql.md) — шаблон для продакшена с шифрованием
- [Обновление токена](examples/token-refresh.md) — промежуточный слой автоматического обновления
- [Проблемы с redirect URI](troubleshooting/redirect-uri-issues.md) — исправление самой частой ошибки
Хочу создать мобильное или одностраничное приложение
- [PKCE](concepts/pkce.md) — почему PKCE обязателен для публичных клиентов
- [Реализация PKCE](examples/pkce-implementation.md) — полный пример кода
- [Параметр state](concepts/state-parameter.md) — защита от CSRF
Хочу создать приложение для устройств без браузера (ТВ, киоск)
- [Потоки OAuth](concepts/oauth-flows.md#device-authorization-flow) — объяснение потока устройства
- [Пример потока устройства](examples/device-flow.md) — полная реализация опроса
- [Частые ошибки](troubleshooting/common-errors.md) — ошибки, характерные для устройств
Я создаю чат-бот для Team Chat
- [Потоки OAuth](concepts/oauth-flows.md#client-authorization-chatbot) — объяснение потока для чат-бота
- [Базовый S2S OAuth](examples/s2s-oauth-basic.md) — похожий шаблон, другой тип доступа
- [Архитектура областей доступа](concepts/scopes-architecture.md) — области доступа для чат-ботов
Получаю ошибки redirect URI (4709)
- [Проблемы с redirect URI](troubleshooting/redirect-uri-issues.md) — НАЧНИ ЗДЕСЬ!
- [Частые ошибки](troubleshooting/common-errors.md#4709-redirect-uri-mismatch) — подробности об ошибке
- [Базовый пользовательский OAuth](examples/user-oauth-basic.md) — посмотри правильный шаблон
Получаю ошибки токенов (4700–4741)
- [Проблемы с токенами](troubleshooting/token-issues.md) — порядок диагностики
- [Жизненный цикл токена](concepts/token-lifecycle.md) — пойми, как токены истекают
- [Обновление токена](examples/token-refresh.md) — реализуй автоматическое обновление
- [Частые ошибки](troubleshooting/common-errors.md) — таблицы кодов ошибок
Получаю ошибки областей доступа (4711)
- [Проблемы с областями доступа](troubleshooting/scope-issues.md) — причины несовпадения
- [Архитектура областей доступа](concepts/scopes-architecture.md) — классические и детальные
- [Классические области доступа](references/classic-scopes.md) — полный справочник по областям доступа
- [Детальные области доступа](references/granular-scopes.md) — справочник по детальным областям доступа
Мне нужно обновлять токены
- [Жизненный цикл токена](concepts/token-lifecycle.md#refresh-strategy) — когда обновлять
- [Обновление токена](examples/token-refresh.md) — шаблон промежуточного слоя
- [Проблемы с токенами](troubleshooting/token-issues.md#refresh-token-problems) — частые ошибки
Хочу понять разницу между классическими и детальными областями доступа
- [Архитектура областей доступа](concepts/scopes-architecture.md) — полное сравнение
- [Классические области доступа](references/classic-scopes.md) — формат
resource:level - [Детальные области доступа](references/granular-scopes.md) — формат
service:action:data_claim:access
Мне нужно защитить свою реализацию OAuth
- [PKCE](concepts/pkce.md) — защита публичных клиентов
- [Параметр state](concepts/state-parameter.md) — защита от CSRF
- [Пользовательский OAuth с MySQL](examples/user-oauth-mysql.md#token-encryption) — шифрование токенов при хранении
Хочу перейти с приложения JWT на S2S OAuth
- [S2S OAuth с Redis](examples/s2s-oauth-redis.md) — современная замена
- [Жизненный цикл токена](concepts/token-lifecycle.md) — другое поведение токенов
Примечание: тип приложения JWT признан устаревшим в июне 2023 года. Для серверной автоматизации переходи на S2S OAuth.
Самые важные документы
1. Потоки OAuth (ДОКУМЕНТ ДЛЯ ВЫБОРА)
[concepts/oauth-flows.md](concepts/oauth-flows.md)
Пойми, какой из 4 потоков использовать:
- S2S OAuth: серверная автоматизация (твой аккаунт)
- Пользовательский OAuth: SaaS-приложения (пользователи дают тебе разрешение)
- Device Flow: устройства без браузера
- Чат-бот: только боты Team Chat
2. Жизненный цикл токена (САМАЯ ЧАСТАЯ ПРОБЛЕМА)
[concepts/token-lifecycle.md](concepts/token-lifecycle.md)
99 % проблем с OAuth связаны с непониманием:
- Срока действия токена (1 час для всех потоков)
- Ротации refresh-токена (новый refresh-токен нужно сохранять)
- Поведения при отзыве (делает недействительными все токены)
3. Проблемы с redirect URI (САМАЯ ЧАСТАЯ ОШИБКА)
[troubleshooting/redirect-uri-issues.md](troubleshooting/redirect-uri-issues.md)
Ошибка 4709 («Redirect URI mismatch») — ошибка OAuth №1. Значение должно совпадать ТОЧНО (включая завершающий слэш, http или https).
Ключевые выводы
Критичные открытия:
- Ротация refresh-токена
- Каждое обновление возвращает НОВЫЙ refresh-токен
- Старый refresh-токен становится недействительным
- Если новый токен не сохранить, возникнут ошибки 4735
- См.: [Обновление токена](examples/token-refresh.md)
- S2S OAuth использует Redis, пользовательский OAuth — базу данных
- S2S: один токен на весь аккаунт → Redis (временное хранилище)
- Пользовательский: токены для каждого пользователя → база данных (постоянное хранилище)
- См.: [S2S OAuth с Redis](examples/s2s-oauth-redis.md) и [Пользовательский OAuth с MySQL](examples/user-oauth-mysql.md)
- Redirect URI должен совпадать ТОЧНО
- Важен завершающий слэш:
/callback≠/callback/ - Важен протокол:
http://≠https:// - Важен порт:
:3000≠:3001 - См.: [Проблемы с redirect URI](troubleshooting/redirect-uri-issues.md)
- PKCE обязателен для публичных клиентов
- Мобильные приложения НЕ МОГУТ хранить секреты
- Одностраничные приложения НЕ МОГУТ хранить секреты
- PKCE защищает от перехвата кода авторизации
- См.: [PKCE](concepts/pkce.md)
- Параметр state защищает от CSRF
- Сгенерируй случайный state перед перенаправлением
- Сохрани его в сеансе
- Проверь при возврате
- См.: [Параметр state](concepts/state-parameter.md)
- Токены нужно хранить в зашифрованном виде
- НИКОГДА не храни токены открытым текстом
- Используй не менее AES-256
- См.: [Пользовательский OAuth с MySQL](examples/user-oauth-mysql.md#token-encryption)
- Тип приложения JWT устарел (июнь 2023)
- Новые приложения JWT создавать нельзя
- Существующие приложения ещё работают, но со временем их отключат
- Переходи на S2S OAuth или пользовательский OAuth
- Уровни областей доступа определяют требования к авторизации
- Без суффикса (уровень пользователя): авторизовать может любой пользователь
:admin: нужна роль администратора:master: нужен владелец аккаунта (несколько аккаунтов)- См.: [Архитектура областей доступа](concepts/scopes-architecture.md)
- Коды авторизации действуют 5 минут
- Обменивай код на токен сразу
- Не кэшируй коды авторизации
- См.: [Жизненный цикл токена](concepts/token-lifecycle.md#authorization-code-expiration)
- Для потока устройства нужен опрос
- Опрашивай с интервалом, который вернул
/devicecode(обычно 5 с) - Обрабатывай
authorization_pending,slow_down,expired_token - См.: [Поток устройства](examples/device-flow.md)
Краткий справочник
«Какой поток OAuth выбрать?»
→ [Потоки OAuth](concepts/oauth-flows.md)
«Ошибка несовпадения redirect URI (4709)»
→ [Проблемы с redirect URI](troubleshooting/redirect-uri-issues.md)
«Токен просрочен или недействителен»
→ [Проблемы с токенами](troubleshooting/token-issues.md)
«Refresh-токен недействителен (4735)»
→ [Обновление токена](examples/token-refresh.md) — новый refresh-токен нужно сохранять
«Ошибка несовпадения областей доступа (4711)»
→ [Проблемы с областями доступа](troubleshooting/scope-issues.md)
«Как защитить приложение с OAuth?»
→ [PKCE](concepts/pkce.md) + [Параметр state](concepts/state-parameter.md)
«Как реализовать автоматическое обновление?»
→ [Обновление токена](examples/token-refresh.md)
«В чём разница между классическими и детальными областями доступа?»
→ [Архитектура областей доступа](concepts/scopes-architecture.md)
«Какой код ошибки что означает?»
→ [Частые ошибки](troubleshooting/common-errors.md)
Версия документа
Основано на Zoom OAuth API v2 (2024+)
Устарело: тип приложения JWT (июнь 2023)
Приятной разработки!
Помни: начни с [потоков OAuth](concepts/oauth-flows.md), чтобы понять, какой поток подходит для твоего сценария!
Переменные окружения
- Стандартные ключи
.envи то, где найти каждое значение, — в [references/environment-variables.md](references/environment-variables.md).
Перевод: iiuniversitet. Оригинал: https://github.com/anthropics/knowledge-work-plugins/tree/main/partner-built/zoom-plugin/skills/oauth, лицензия MIT. Изменения: перевод на русский язык.
Оригинал на английском
---
name: zoom-oauth
description: Reference skill for Zoom authentication. Use after routing to an auth workflow when choosing app credentials, grant types, scopes, token refresh behavior, or debugging Zoom OAuth failures.
user-invocable: false
triggers:
- zoom oauth
- zoom authentication
- zoom authorization
- server to server oauth
- s2s oauth
- zoom access token
- zoom refresh token
- authorization code flow
- device authorization
- pkce
- zoom api authentication
- oauth error 4709
- oauth error 4733
- oauth error 4735
- redirect uri mismatch
---
# Zoom OAuth
Background reference for Zoom auth and token lifecycle behavior. Prefer `setup-zoom-oauth` first, then use this skill for the exact flow, scope, and error details.
# Zoom OAuth
Authentication and authorization for Zoom APIs.
## 📖 Complete Documentation
For comprehensive guides, production patterns, and troubleshooting, see **Integrated Index section below**.
Quick navigation:
- **[5-Minute Runbook](RUNBOOK.md)** - Preflight checks before deep debugging
- **[OAuth Flows](concepts/oauth-flows.md)** - Which flow to use and how each works
- **[Token Lifecycle](concepts/token-lifecycle.md)** - Expiration, refresh, and revocation
- **[Production Examples](examples/s2s-oauth-redis.md)** - Redis caching, MySQL storage, auto-refresh
- **[Troubleshooting](troubleshooting/common-errors.md)** - Error codes 4700-4741
## Prerequisites
- Zoom app created in [Marketplace](https://marketplace.zoom.us/)
- Client ID and Client Secret
- For S2S OAuth: Account ID
## Four Authorization Use Cases
| Use Case | App Type | Grant Type | Industry Name |
|----------|----------|------------|---------------|
| **Account Authorization** | Server-to-Server | `account_credentials` | Client Credentials Grant, M2M, Two-legged OAuth |
| **User Authorization** | General | `authorization_code` | Authorization Code Grant, Three-legged OAuth |
| **Device Authorization** | General | `urn:ietf:params:oauth:grant-type:device_code` | Device Authorization Grant (RFC 8628) |
| **Client Authorization** | General | `client_credentials` | Client Credentials Grant (chatbot-scoped) |
### Industry Terminology
| Term | Meaning |
|------|---------|
| **Two-legged OAuth** | No user involved (client ↔ server) |
| **Three-legged OAuth** | User involved (user ↔ client ↔ server) |
| **M2M** | Machine-to-Machine (backend services) |
| **Public client** | Can't keep secrets (mobile, SPA) → use PKCE |
| **Confidential client** | Can keep secrets (backend servers) |
| **PKCE** | Proof Key for Code Exchange (RFC 7636), pronounced "pixy" |
### Which Flow Should I Use?
```
┌─────────────────────┐
│ What are you │
│ building? │
└──────────┬──────────┘
│
┌────────────────────┼────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Backend │ │ App for other │ │ Chatbot only │
│ automation │ │ users/accounts │ │ (Team Chat) │
│ (your account) │ │ │ │ │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
▼ │ ▼
┌─────────────────┐ │ ┌─────────────────┐
│ ACCOUNT │ │ │ CLIENT │
│ (S2S OAuth) │ │ │ (Chatbot) │
└─────────────────┘ │ └─────────────────┘
│
▼
┌─────────────────────┐
│ Does device have │
│ a browser? │
└──────────┬──────────┘
│
┌───────────────┴───────────────┐
│ NO YES│
▼ ▼
┌─────────────────────────┐ ┌─────────────────┐
│ DEVICE │ │ USER │
│ (Device Flow) │ │ (Auth Code) │
│ │ │ │
│ Examples: │ │ + PKCE if │
│ • Smart TV │ │ public client │
│ • Meeting SDK device │ │ │
└─────────────────────────┘ └─────────────────┘
```
---
## Account Authorization (Server-to-Server OAuth)
For backend automation without user interaction.
### Request Access Token
```bash
POST https://zoom.us/oauth/token?grant_type=account_credentials&account_id={ACCOUNT_ID}
Headers:
Authorization: Basic {Base64(ClientID:ClientSecret)}
```
### Response
```json
{
"access_token": "eyJ...",
"token_type": "bearer",
"expires_in": 3600,
"scope": "user:read:user:admin",
"api_url": "https://api.zoom.us"
}
```
### Refresh
Access tokens expire after **1 hour**. No separate refresh flow - just request a new token.
---
## User Authorization (Authorization Code Flow)
For apps that act on behalf of users.
### Step 1: Redirect User to Authorize
```
https://zoom.us/oauth/authorize?response_type=code&client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}
```
Use `https://zoom.us/oauth/authorize` for consent, but `https://zoom.us/oauth/token` for token exchange.
**Optional Parameters:**
| Parameter | Description |
|-----------|-------------|
| `state` | CSRF protection, maintains state through flow |
| `code_challenge` | For PKCE (see below) |
| `code_challenge_method` | `S256` or `plain` (default: plain) |
### Step 2: User Authorizes
- User signs in and grants permission
- Redirects to `redirect_uri` with authorization code:
```
https://example.com/?code={AUTHORIZATION_CODE}
```
### Step 3: Exchange Code for Token
```bash
POST https://zoom.us/oauth/token?grant_type=authorization_code&code={CODE}&redirect_uri={REDIRECT_URI}
Headers:
Authorization: Basic {Base64(ClientID:ClientSecret)}
```
**With PKCE:** Add `code_verifier` parameter.
### Response
```json
{
"access_token": "eyJ...",
"token_type": "bearer",
"refresh_token": "eyJ...",
"expires_in": 3600,
"scope": "user:read:user",
"api_url": "https://api.zoom.us"
}
```
### Refresh Token
```bash
POST https://zoom.us/oauth/token?grant_type=refresh_token&refresh_token={REFRESH_TOKEN}
Headers:
Authorization: Basic {Base64(ClientID:ClientSecret)}
```
- Access tokens expire after **1 hour**
- Refresh token lifetime can vary; ~90 days is common for some user-based flows. Treat it as configuration/behavior that can change and rely on runtime errors + re-auth fallback.
- Always use the latest refresh token for the next request
- If refresh token expires, redirect user to authorization URL to restart flow
### User-Level vs Account-Level Apps
| Type | Who Can Authorize | Scope Access |
|------|-------------------|--------------|
| **User-level** | Any individual user | Scoped to themselves |
| **Account-level** | User with admin permissions | Account-wide access (admin scopes) |
---
## Device Authorization (Device Flow)
For devices without browsers (e.g., Meeting SDK apps).
### Prerequisites
Enable "Use App on Device" in: Features > Embed > Enable Meeting SDK
### Step 1: Request Device Code
```bash
POST https://zoom.us/oauth/devicecode?client_id={CLIENT_ID}
Headers:
Authorization: Basic {Base64(ClientID:ClientSecret)}
```
### Response
```json
{
"device_code": "DEVICE_CODE",
"user_code": "abcd1234",
"verification_uri": "https://zoom.us/oauth_device",
"verification_uri_complete": "https://zoom.us/oauth/device/complete/{CODE}",
"expires_in": 900,
"interval": 5
}
```
### Step 2: User Authorization
Direct user to:
- `verification_uri` and display `user_code` for manual entry, OR
- `verification_uri_complete` (user code prefilled)
User signs in and allows the app.
### Step 3: Poll for Token
Poll at the `interval` (5 seconds) until user authorizes:
```bash
POST https://zoom.us/oauth/token?grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code={DEVICE_CODE}
Headers:
Authorization: Basic {Base64(ClientID:ClientSecret)}
```
### Response
```json
{
"access_token": "eyJ...",
"token_type": "bearer",
"refresh_token": "eyJ...",
"expires_in": 3599,
"scope": "user:read:user user:read:token",
"api_url": "https://api.zoom.us"
}
```
### Polling Responses
| Response | Meaning | Action |
|----------|---------|--------|
| Token returned | User authorized | Store tokens, done |
| `error: authorization_pending` | User hasn't authorized yet | Keep polling at interval |
| `error: slow_down` | Polling too fast | Increase interval by 5 seconds |
| `error: expired_token` | Device code expired (15 min) | Restart flow from Step 1 |
| `error: access_denied` | User denied authorization | Handle denial, don't retry |
### Polling Implementation
```javascript
async function pollForToken(deviceCode, interval) {
while (true) {
await sleep(interval * 1000);
try {
const response = await axios.post(
`https://zoom.us/oauth/token?grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code=${deviceCode}`,
null,
{ headers: { 'Authorization': `Basic ${credentials}` } }
);
return response.data; // Success - got tokens
} catch (error) {
const err = error.response?.data?.error;
if (err === 'authorization_pending') continue;
if (err === 'slow_down') { interval += 5; continue; }
throw error; // expired_token or access_denied
}
}
}
```
### Refresh
Same as User Authorization. If refresh token expires, restart device flow from Step 1.
---
## Client Authorization (Chatbot)
For chatbot message operations only.
### Request Token
```bash
POST https://zoom.us/oauth/token?grant_type=client_credentials
Headers:
Authorization: Basic {Base64(ClientID:ClientSecret)}
```
### Response
```json
{
"access_token": "eyJ...",
"token_type": "bearer",
"expires_in": 3600,
"scope": "imchat:bot",
"api_url": "https://api.zoom.us"
}
```
### Refresh
Tokens expire after **1 hour**. No refresh flow - just request a new token.
---
## Using Access Tokens
### Call API
```bash
GET https://api.zoom.us/v2/users/me
Headers:
Authorization: Bearer {ACCESS_TOKEN}
```
### Me Context
Replace `userID` with `me` to target the token's associated user:
| Endpoint | Methods |
|----------|---------|
| `/v2/users/me` | GET, PATCH |
| `/v2/users/me/token` | GET |
| `/v2/users/me/meetings` | GET, POST |
---
## Revoke Access Token
Works for all authorization types.
```bash
POST https://zoom.us/oauth/revoke?token={ACCESS_TOKEN}
Headers:
Authorization: Basic {Base64(ClientID:ClientSecret)}
```
### Response
```json
{
"status": "success"
}
```
---
## PKCE (Proof Key for Code Exchange)
For public clients that can't securely store secrets (mobile apps, SPAs, desktop apps).
### When to Use PKCE
| Client Type | Use PKCE? | Why |
|-------------|-----------|-----|
| Mobile app | **Yes** | Can't securely store client secret |
| Single Page App (SPA) | **Yes** | JavaScript is visible to users |
| Desktop app | **Yes** | Binary can be decompiled |
| Meeting SDK (client-side) | **Yes** | Runs on user's device |
| Backend server | Optional | Can keep secrets, but PKCE adds security |
### How PKCE Works
```
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Client │ │ Zoom │ │ Zoom │
│ App │ │ Auth │ │ Token │
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
│ 1. Generate code_verifier (random) │ │
│ 2. Create code_challenge = SHA256(verifier) │
│ │ │
│ ─────── /authorize + code_challenge ──► │ │
│ │ │
│ ◄────── authorization_code ──────────── │ │
│ │ │
│ ─────────────── /token + code_verifier ─┼────────────────────────────► │
│ │ │
│ │ Verify: SHA256(verifier) │
│ │ == challenge │
│ │ │
│ ◄───────────────────────────────────────┼─────── access_token ──────── │
│ │ │
```
### Implementation (Node.js)
```javascript
const crypto = require('crypto');
function generatePKCE() {
const verifier = crypto.randomBytes(32).toString('base64url');
const challenge = crypto.createHash('sha256').update(verifier).digest('base64url');
return { verifier, challenge };
}
const pkce = generatePKCE();
const authUrl = `https://zoom.us/oauth/authorize?` +
`response_type=code&` +
`client_id=${CLIENT_ID}&` +
`redirect_uri=${REDIRECT_URI}&` +
`code_challenge=${pkce.challenge}&` +
`code_challenge_method=S256`;
// Store pkce.verifier in session for callback
```
### Token Exchange with PKCE
```bash
POST https://zoom.us/oauth/token?grant_type=authorization_code&code={CODE}&redirect_uri={REDIRECT_URI}&code_verifier={VERIFIER}
Headers:
Authorization: Basic {Base64(ClientID:ClientSecret)}
```
---
## Deauthorization
When a user removes your app, Zoom sends a webhook to your Deauthorization Notification Endpoint URL.
### Webhook Event
```json
{
"event": "app_deauthorized",
"event_ts": 1740439732278,
"payload": {
"account_id": "ACCOUNT_ID",
"user_id": "USER_ID",
"signature": "SIGNATURE",
"deauthorization_time": "2019-06-17T13:52:28.632Z",
"client_id": "CLIENT_ID"
}
}
```
### Requirements
- **Delete all associated user data** after receiving this event
- **Verify webhook signature** (use secret token, verification token deprecated Oct 2023)
- Only public apps receive deauthorization webhooks (not private/dev apps)
---
## Pre-Approval Flow
Some Zoom accounts require Marketplace admin pre-approval before users can authorize apps.
- Users can request pre-approval from their admin
- Account-level apps (admin scopes) require appropriate role permissions
---
## Active Apps Notifier (AAN)
In-meeting feature showing apps with real-time access to content.
- Displays icon + tooltip with app info, content type being accessed, approving account
- Supported: Zoom client 5.6.7+, Meeting SDK 5.9.0+
---
## OAuth Scopes
### Scope Types
| Type | Description | For |
|------|-------------|-----|
| **Classic scopes** | Legacy scopes (user, admin, master levels) | Existing apps |
| **Granular scopes** | New fine-grained scopes with optional support | New apps |
### Classic Scopes
For previously-created apps. Three levels:
- **User-level**: Access to individual user's data
- **Admin-level**: Account-wide access, requires admin role
- **Master-level**: For master-sub account setups, requires account owner
Full list: https://developers.zoom.us/docs/integrations/oauth-scopes/
### Granular Scopes
For new apps. Format: `<service>:<action>:<data_claim>:<access>`
| Component | Values |
|-----------|--------|
| **service** | `meeting`, `webinar`, `user`, `recording`, etc. |
| **action** | `read`, `write`, `update`, `delete` |
| **data_claim** | Data category (e.g., `participants`, `settings`) |
| **access** | empty (user), `admin`, `master` |
Example: `meeting:read:list_meetings:admin`
Full list: https://developers.zoom.us/docs/integrations/oauth-scopes-granular/
### Optional Scopes
Granular scopes can be marked as **optional** - users choose whether to grant them.
**Basic authorization** (uses build flow defaults):
```
https://zoom.us/oauth/authorize?response_type=code&client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}
```
**Advanced authorization** (custom scopes per request):
```
https://zoom.us/oauth/authorize?client_id={CLIENT_ID}&response_type=code&redirect_uri={REDIRECT_URI}&scope={required_scopes}&optional_scope={optional_scopes}
```
**Include previously granted scopes:**
```
https://zoom.us/oauth/authorize?...&include_granted_scopes&scope={additional_scopes}
```
### Migrating Classic to Granular
1. Manage > select app > edit
2. Scope page > Development tab > click **Migrate**
3. Review auto-assigned granular scopes, remove unnecessary, mark optional
4. Test
5. Production tab > click **Migrate**
**Notes:**
- No review needed if only migrating or reducing scopes
- Existing user tokens continue with classic scope values until re-authorization
- New users get granular scopes after migration
---
## Common Error Codes
| Code | Message | Solution |
|------|---------|----------|
| 4700 | Token cannot be empty | Check Authorization header has valid token |
| 4702/4704 | Invalid client | Verify Client ID and Client Secret |
| 4705 | Grant type not supported | Use: `account_credentials`, `authorization_code`, `urn:ietf:params:oauth:grant-type:device_code`, or `client_credentials` |
| 4706 | Client ID or secret missing | Add credentials to header or request params |
| 4709 | Redirect URI mismatch | Ensure redirect_uri matches app configuration exactly (including trailing slash) |
| 4711 | Refresh token invalid | Token scopes don't match client scopes |
| 4717 | App has been disabled | Contact Zoom support |
| 4733 | Code is expired | Authorization codes expire in 5 minutes - restart flow |
| 4734 | Invalid authorization code | Regenerate authorization code |
| 4735 | Owner of token does not exist | User was removed from account - re-authorize |
| 4741 | Token has been revoked | Use the most recent token from latest authorization |
See `references/oauth-errors.md` for complete error list.
---
## Quick Reference
| Flow | Grant Type | Token Expiry | Refresh |
|------|------------|--------------|---------|
| Account (S2S) | `account_credentials` | 1 hour | Request new token |
| User | `authorization_code` | 1 hour | Use refresh_token (90 day expiry) |
| Device | `urn:ietf:params:oauth:grant-type:device_code` | 1 hour | Use refresh_token (90 day expiry) |
| Client (Chatbot) | `client_credentials` | 1 hour | Request new token |
---
## Demo Guidance
If you build an OAuth demo app, document its runtime base URL in that demo project's own
README or `.env.example`, not in this shared skill.
## Resources
- **OAuth docs**: https://developers.zoom.us/docs/integrations/oauth/
- **S2S OAuth docs**: https://developers.zoom.us/docs/internal-apps/s2s-oauth/
- **PKCE blog**: https://developers.zoom.us/blog/pcke-oauth-with-postman-rest-api/
- **Classic scopes**: https://developers.zoom.us/docs/integrations/oauth-scopes/
- **Granular scopes**: https://developers.zoom.us/docs/integrations/oauth-scopes-granular/
---
## Integrated Index
_This section was migrated from `SKILL.md`._
## Quick Start Path
**If you're new to Zoom OAuth, follow this order:**
1. **Run preflight checks first** → [RUNBOOK.md](RUNBOOK.md)
2. **Choose your OAuth flow** → [concepts/oauth-flows.md](concepts/oauth-flows.md)
- 4 flows: S2S (backend), User (SaaS), Device (no browser), Chatbot
- Decision matrix: Which flow fits your use case?
3. **Understand token lifecycle** → [concepts/token-lifecycle.md](concepts/token-lifecycle.md)
- **CRITICAL**: How tokens expire, refresh, and revoke
- Common pitfalls: refresh token rotation
4. **Implement your flow** → Jump to examples:
- Backend automation → [examples/s2s-oauth-redis.md](examples/s2s-oauth-redis.md)
- SaaS app → [examples/user-oauth-mysql.md](examples/user-oauth-mysql.md)
- Mobile/SPA → [examples/pkce-implementation.md](examples/pkce-implementation.md)
- Device (TV/kiosk) → [examples/device-flow.md](examples/device-flow.md)
5. **Fix redirect URI issues** → [troubleshooting/redirect-uri-issues.md](troubleshooting/redirect-uri-issues.md)
- Most common OAuth error: Redirect URI mismatch
6. **Implement token refresh** → [examples/token-refresh.md](examples/token-refresh.md)
- Automatic middleware pattern
- Handle refresh token rotation
7. **Troubleshoot errors** → [troubleshooting/common-errors.md](troubleshooting/common-errors.md)
- Error code tables (4700-4741 range)
- Quick diagnostic workflow
---
## Documentation Structure
```
oauth/
├── SKILL.md # Main skill overview
├── SKILL.md # This file - navigation guide
│
├── concepts/ # Core OAuth concepts
│ ├── oauth-flows.md # 4 flows: S2S, User, Device, Chatbot
│ ├── token-lifecycle.md # Expiration, refresh, revocation
│ ├── pkce.md # PKCE security for public clients
│ ├── scopes-architecture.md # Classic vs Granular scopes
│ └── state-parameter.md # CSRF protection with state
│
├── examples/ # Complete working code
│ ├── s2s-oauth-basic.md # S2S OAuth minimal example
│ ├── s2s-oauth-redis.md # S2S OAuth with Redis caching (production)
│ ├── user-oauth-basic.md # User OAuth minimal example
│ ├── user-oauth-mysql.md # User OAuth with MySQL + encryption (production)
│ ├── device-flow.md # Device authorization flow
│ ├── pkce-implementation.md # PKCE for SPAs/mobile apps
│ └── token-refresh.md # Auto-refresh middleware pattern
│
├── troubleshooting/ # Problem solving guides
│ ├── common-errors.md # Error codes 4700-4741
│ ├── redirect-uri-issues.md # Most common OAuth error
│ ├── token-issues.md # Expired, revoked, invalid tokens
│ └── scope-issues.md # Scope mismatch errors
│
└── references/ # Reference documentation
├── oauth-errors.md # Complete error code reference
├── classic-scopes.md # Classic scope reference
└── granular-scopes.md # Granular scope reference
```
---
## By Use Case
### I want to automate Zoom tasks on my own account
1. [OAuth Flows](concepts/oauth-flows.md#server-to-server-s2s-oauth) - S2S OAuth explained
2. [S2S OAuth Redis](examples/s2s-oauth-redis.md) - Production pattern with Redis caching
3. [Token Lifecycle](concepts/token-lifecycle.md) - 1hr token, no refresh
### I want to build a SaaS app for other Zoom users
1. [OAuth Flows](concepts/oauth-flows.md#user-authorization-oauth) - User OAuth explained
2. [User OAuth MySQL](examples/user-oauth-mysql.md) - Production pattern with encryption
3. [Token Refresh](examples/token-refresh.md) - Automatic refresh middleware
4. [Redirect URI Issues](troubleshooting/redirect-uri-issues.md) - Fix most common error
### I want to build a mobile or SPA app
1. [PKCE](concepts/pkce.md) - Why PKCE is required for public clients
2. [PKCE Implementation](examples/pkce-implementation.md) - Complete code example
3. [State Parameter](concepts/state-parameter.md) - CSRF protection
### I want to build an app for devices without browsers (TV, kiosk)
1. [OAuth Flows](concepts/oauth-flows.md#device-authorization-flow) - Device flow explained
2. [Device Flow Example](examples/device-flow.md) - Complete polling implementation
3. [Common Errors](troubleshooting/common-errors.md) - Device-specific errors
### I'm building a Team Chat bot
1. [OAuth Flows](concepts/oauth-flows.md#client-authorization-chatbot) - Chatbot flow explained
2. [S2S OAuth Basic](examples/s2s-oauth-basic.md) - Similar pattern, different grant type
3. [Scopes Architecture](concepts/scopes-architecture.md) - Chatbot-specific scopes
### I'm getting redirect URI errors (4709)
1. [Redirect URI Issues](troubleshooting/redirect-uri-issues.md) - **START HERE!**
2. [Common Errors](troubleshooting/common-errors.md#4709-redirect-uri-mismatch) - Error details
3. [User OAuth Basic](examples/user-oauth-basic.md) - See correct pattern
### I'm getting token errors (4700-4741)
1. [Token Issues](troubleshooting/token-issues.md) - Diagnostic workflow
2. [Token Lifecycle](concepts/token-lifecycle.md) - Understand expiration
3. [Token Refresh](examples/token-refresh.md) - Implement auto-refresh
4. [Common Errors](troubleshooting/common-errors.md) - Error code tables
### I'm getting scope errors (4711)
1. [Scope Issues](troubleshooting/scope-issues.md) - Mismatch causes
2. [Scopes Architecture](concepts/scopes-architecture.md) - Classic vs Granular
3. [Classic Scopes](references/classic-scopes.md) - Complete scope reference
4. [Granular Scopes](references/granular-scopes.md) - Granular scope reference
### I need to refresh tokens
1. [Token Lifecycle](concepts/token-lifecycle.md#refresh-strategy) - When to refresh
2. [Token Refresh](examples/token-refresh.md) - Middleware pattern
3. [Token Issues](troubleshooting/token-issues.md#refresh-token-problems) - Common mistakes
### I want to understand the difference between Classic and Granular scopes
1. [Scopes Architecture](concepts/scopes-architecture.md) - **Complete comparison**
2. [Classic Scopes](references/classic-scopes.md) - `resource:level` format
3. [Granular Scopes](references/granular-scopes.md) - `service:action:data_claim:access` format
### I need to secure my OAuth implementation
1. [PKCE](concepts/pkce.md) - Public client security
2. [State Parameter](concepts/state-parameter.md) - CSRF protection
3. [User OAuth MySQL](examples/user-oauth-mysql.md#token-encryption) - Token encryption at rest
### I want to migrate from JWT app to S2S OAuth
1. [S2S OAuth Redis](examples/s2s-oauth-redis.md) - Modern replacement
2. [Token Lifecycle](concepts/token-lifecycle.md) - Different token behavior
> **Note**: JWT App Type was deprecated in June 2023. Migrate to S2S OAuth for server-to-server automation.
---
## Most Critical Documents
### 1. OAuth Flows (DECISION DOCUMENT)
**[concepts/oauth-flows.md](concepts/oauth-flows.md)**
Understand which of the 4 flows to use:
- **S2S OAuth**: Backend automation (your account)
- **User OAuth**: SaaS apps (users authorize you)
- **Device Flow**: Devices without browsers
- **Chatbot**: Team Chat bots only
### 2. Token Lifecycle (MOST COMMON ISSUE)
**[concepts/token-lifecycle.md](concepts/token-lifecycle.md)**
99% of OAuth issues stem from misunderstanding:
- Token expiration (1 hour for all flows)
- Refresh token rotation (must save new refresh token)
- Revocation behavior (invalidates all tokens)
### 3. Redirect URI Issues (MOST COMMON ERROR)
**[troubleshooting/redirect-uri-issues.md](troubleshooting/redirect-uri-issues.md)**
Error 4709 ("Redirect URI mismatch") is the #1 OAuth error.
Must match EXACTLY (including trailing slash, http vs https).
---
## Key Learnings
### Critical Discoveries:
1. **Refresh Token Rotation**
- Each refresh returns a NEW refresh token
- Old refresh token becomes invalid
- Failure to save new token causes 4735 errors
- See: [Token Refresh](examples/token-refresh.md)
2. **S2S OAuth Uses Redis, User OAuth Uses Database**
- S2S: Single token for entire account → Redis (ephemeral)
- User: Per-user tokens → Database (persistent)
- See: [S2S OAuth Redis](examples/s2s-oauth-redis.md) vs [User OAuth MySQL](examples/user-oauth-mysql.md)
3. **Redirect URI Must Match EXACTLY**
- Trailing slash matters: `/callback` ≠ `/callback/`
- Protocol matters: `http://` ≠ `https://`
- Port matters: `:3000` ≠ `:3001`
- See: [Redirect URI Issues](troubleshooting/redirect-uri-issues.md)
4. **PKCE Required for Public Clients**
- Mobile apps CANNOT keep secrets
- SPAs CANNOT keep secrets
- PKCE prevents authorization code interception
- See: [PKCE](concepts/pkce.md)
5. **State Parameter Prevents CSRF**
- Generate random state before redirect
- Store in session
- Verify on callback
- See: [State Parameter](concepts/state-parameter.md)
6. **Token Storage Must Be Encrypted**
- NEVER store tokens in plain text
- Use AES-256 minimum
- See: [User OAuth MySQL](examples/user-oauth-mysql.md#token-encryption)
7. **JWT App Type is Deprecated (June 2023)**
- No new JWT apps can be created
- Existing apps still work but will eventually be sunset
- Migrate to S2S OAuth or User OAuth
8. **Scope Levels Determine Authorization Requirements**
- No suffix (user-level): Any user can authorize
- `:admin`: Requires admin role
- `:master`: Requires account owner (multi-account)
- See: [Scopes Architecture](concepts/scopes-architecture.md)
9. **Authorization Codes Expire in 5 Minutes**
- Exchange code for token immediately
- Don't cache authorization codes
- See: [Token Lifecycle](concepts/token-lifecycle.md#authorization-code-expiration)
10. **Device Flow Requires Polling**
- Poll at interval returned by `/devicecode` (usually 5s)
- Handle `authorization_pending`, `slow_down`, `expired_token`
- See: [Device Flow](examples/device-flow.md)
---
## Quick Reference
### "Which OAuth flow should I use?"
→ [OAuth Flows](concepts/oauth-flows.md)
### "Redirect URI mismatch error (4709)"
→ [Redirect URI Issues](troubleshooting/redirect-uri-issues.md)
### "Token expired or invalid"
→ [Token Issues](troubleshooting/token-issues.md)
### "Refresh token invalid (4735)"
→ [Token Refresh](examples/token-refresh.md) - Must save new refresh token
### "Scope mismatch error (4711)"
→ [Scope Issues](troubleshooting/scope-issues.md)
### "How do I secure my OAuth app?"
→ [PKCE](concepts/pkce.md) + [State Parameter](concepts/state-parameter.md)
### "How do I implement auto-refresh?"
→ [Token Refresh](examples/token-refresh.md)
### "What's the difference between Classic and Granular scopes?"
→ [Scopes Architecture](concepts/scopes-architecture.md)
### "What error code means what?"
→ [Common Errors](troubleshooting/common-errors.md)
---
## Document Version
Based on **Zoom OAuth API v2** (2024+)
**Deprecated:** JWT App Type (June 2023)
---
**Happy coding!**
Remember: Start with [OAuth Flows](concepts/oauth-flows.md) to understand which flow fits your use case!
## Environment Variables
- See [references/environment-variables.md](references/environment-variables.md) for standardized `.env` keys and where to find each value.
Источник: anthropics/knowledge-work-plugins / zoom-plugin / zoom-oauth ↗. Ссылка проверена 2026-10-10.