)
Zoom REST API 架构指南Base URL、区域路由、me关键字与 UUID 双重编码knowledge-work-plugins zoom-plugin 实战解析【免费下载链接】knowledge-work-pluginsOpen source repository of plugins primarily intended for knowledge workers to use in Claude Cowork项目地址: https://gitcode.com/GitHub_Trending/kn/knowledge-work-plugins本文以 knowledge-work-plugins 仓库中 zoom-plugin 的 rest-api 技能核心概念文档 api-architecture.md 为骨架系统讲解调用 Zoom REST API 前必须掌握的设计约定Base URL 与区域路由、me关键字的应用类型差异、Meeting ID 与 UUID 的选择及双重 URL 编码、ISO 8601 时间格式、录音下载 URL 的鉴权与重定向以及共享访问权限等边界规则。读完本文你将具备在 Server-to-Server OAuth 与 User OAuth 两种场景下正确拼装请求地址、规避常见 401/403/404 错误、安全下载云端录制文件的能力。文档定位为什么它是整个技能体系的地基在 rest-api 技能中api-architecture.md 被 SKILL.md 明确标注为FOUNDATION基础与最关键的文档之一排在快速入门路径的第一步先理解 API 设计Base URL、区域端点、me关键字规则、ID 与 UUID、时间格式再进入认证、会议生命周期、限流与 Webhook。也就是说凡是基于 Zoom REST API 构建的服务端自动化创建会议、管理用户、拉取录制、生成报表都要先吃透本文的约定否则后续每个请求都可能踩坑。本文涉及的配套文档以下均为仓库根目录相对路径认证流程authentication-flows.md、认证参考会议全生命周期示例meeting-lifecycle.md录制下载管线recording-pipeline.md限流策略rate-limiting-strategy.md错误排查common-errors.mdOAuth 完整实现zoom-oauth 技能Base URLREST 与 GraphQL 两个独立入口Zoom REST API 的所有请求都走 HTTPS并在路径中携带 API 版本号/v2https://api.zoom.us/v2/例如创建会议、查询用户、拉取录制等 600 个 REST 端点都以此为基础前缀。在 SKILL.md 的快速开始中创建会议的请求即形如curl -X POST https://api.zoom.us/v2/users/HOST_USER_ID/meetings \ -H Authorization: Bearer ACCESS_TOKEN \ -H Content-Type: application/json \ -d {...}而GraphQL使用独立的版本化端点不在/v2之下https://api.zoom.us/v3/graphqlGraphQL 是单端点、基于游标分页的备选查询接口beta每个字段相当于一次 REST 调用对应的配额具体用法可参见 rest-api 技能下的 graphql-queries.md 与 graphql 参考。区域 Base URL从 OAuth 令牌响应中读取api_urlZoom 出于数据驻留data residency合规要求允许不同地区的用户数据存放在对应的区域数据中心。OAuth 令牌响应中会返回一个api_url字段指示该用户的数据所在区域{ access_token: eyJ..., api_url: https://api-eu.zoom.us }正确的做法是拿到api_url后在其后追加/v2/构造出区域 Base URL。各区域对照如下RegionAPI URLBase URLGlobal (default)https://api.zoom.ushttps://api.zoom.us/v2Australiahttps://api-au.zoom.ushttps://api-au.zoom.us/v2Canadahttps://api-ca.zoom.ushttps://api-ca.zoom.us/v2European Unionhttps://api-eu.zoom.ushttps://api-eu.zoom.us/v2Indiahttps://api-in.zoom.ushttps://api-in.zoom.us/v2Saudi Arabiahttps://api-sa.zoom.ushttps://api-sa.zoom.us/v2Singaporehttps://api-sg.zoom.ushttps://api-sg.zoom.us/v2United Kingdomhttps://api-uk.zoom.ushttps://api-uk.zoom.us/v2United Stateshttps://api-us.zoom.ushttps://api-us.zoom.us/v2Vanity accounthttps://{vanity}.zoom.ushttps://{vanity}.zoom.us/v2重要提示全局地址https://api.zoom.us无论用户位于哪个区域都始终可用。区域 URL 仅用于合规要求并非强制——即使你忽略api_url直接使用全局地址请求也能成功只是可能不满足特定地区的数据驻留合规。Node.js 实现从令牌动态推导 Base URL原始文档给出了一个完整的 Node.js 客户端工厂函数它先换取令牌再从api_url动态决定 Base URL值得完整保留async function getZoomClient(accountId, clientId, clientSecret) { const credentials Buffer.from(${clientId}:${clientSecret}).toString(base64); const tokenRes await fetch(https://zoom.us/oauth/token, { method: POST, headers: { Authorization: Basic ${credentials}, Content-Type: application/x-www-form-urlencoded }, body: grant_typeaccount_credentialsaccount_id${accountId} }); const tokenData await tokenRes.json(); const baseUrl tokenData.api_url ? ${tokenData.api_url}/v2 : https://api.zoom.us/v2; return { accessToken: tokenData.access_token, baseUrl, async request(method, path, body null) { const res await fetch(${this.baseUrl}${path}, { method, headers: { Authorization: Bearer ${this.accessToken}, Content-Type: application/json }, body: body ? JSON.stringify(body) : undefined }); if (!res.ok) { const err await res.json(); throw new Error(Zoom API ${res.status}: ${err.message}); } return res.json(); } }; }这段代码对应的是 Server-to-Server OAuth 的account_credentials授权方式两脚 OAuth令牌有效期 1 小时expires_in为 3600 秒。如果要在生产环境做令牌缓存与自动刷新可参考 authentication-flows.md 中带 60 秒缓冲的ZoomS2SAuth令牌管理器以及 oauth 技能 中的 Redis 缓存、MySQL 存储与自动刷新示例。me关键字按应用类型严格区分否则直接报错URL 路径中的me是userId/accountId的替身但它的行为因应用类型而异这是 Zoom REST API 最容易踩的坑之一App TypemeBehaviorWhen to UseUser-level OAuthResolves to the authenticated userMUST use— providinguserIdcauses invalid token errorServer-to-Server OAuthNot supportedMUST NOT use— provide actualuserIdor emailAccount-level OAuthResolves to the user who installed the appCan use eithermeoruserId示例# User OAuth app — MUST use me GET /v2/users/me GET /v2/users/me/meetings # S2S OAuth app — MUST use actual userId or email GET /v2/users/abc123def GET /v2/users/johnexample.com GET /v2/users/johnexample.com/meetings常见错误与修复如果你在 User OAuth 应用里误用了userId会得到如下 401 错误{ code: 4700, message: Invalid access token, does not contain scopes. }修复把路径中的userId替换为me。同样的教训在 authentication-flows.md 中被再次强调User OAuth appsmustusemeinstead ofuserId并在 common-errors.md 中作为 404code 1001 User does not exist与 401code 4700的典型案例给出正误对照。在 SKILL.md 的快速开始中也能看到 S2S 场景的对应用法POST /v2/users/HOST_USER_ID/meetings即 S2S 必须显式写主机用户 ID 或邮箱不能用me。Meeting ID 与 UUID语义不同编码不同Meeting ID会议的数字标识符可复用于周期性会议recurring meeting最后一次使用后30 天过期。UUID某个具体会议实例的唯一标识永不过期周期性会议的每次发生都会生成一个新的 UUID。何时用哪个Use CaseUseGet a scheduled meetingMeeting IDGet a past meeting instanceUUIDGet recordings for a specific sessionUUIDReport on a specific occurrenceUUID在 meeting-lifecycle.md 的创建响应中可以看到两者同时出现id: 93123456789是 Meeting IDuuid: xyzAbC1234是实例 UUIDjoin_url中携带的正是数字 ID。UUID 双重 URL 编码关键陷阱以/开头或包含//的 UUID必须双重 URL 编码。因为单次编码后路径中残留的%2F表示/会被部分服务再解码一次导致路径语义被破坏。正确做法是调用两次encodeURIComponent。function encodeUUID(uuid) { // Check if double-encoding is needed if (uuid.startsWith(/) || uuid.includes(//)) { return encodeURIComponent(encodeURIComponent(uuid)); } return encodeURIComponent(uuid); } // UUID: /abcABC123 // Single encode: %2FabcABC123%3D%3D // Double encode: %252FabcABC123%253D%253D ← Required const meetingUUID /abcABC123; const url https://api.zoom.us/v2/past_meetings/${encodeUUID(meetingUUID)};Python 侧使用urllib.parse.quote实现同样的双重编码注意safe确保/与都被编码from urllib.parse import quote def encode_uuid(uuid_str): if uuid_str.startswith(/) or // in uuid_str: return quote(quote(uuid_str, safe), safe) return quote(uuid_str, safe) uuid /abcABC123 url fhttps://api.zoom.us/v2/past_meetings/{encode_uuid(uuid)}在 common-errors.md 中Meeting not foundcode 300的排查清单里也明确包含UUID 编码错误其解法正是双重编码。因此当你遇到 404 且 ID 看起来像 UUID 时第一反应应是检查编码方式。时间格式UTC 与本地时间的 ISO 8601 两种变体Zoom API 使用 ISO 8601但存在两种变体混用是字段校验失败的常见原因FormatMeaningExampleyyyy-MM-ddTHH:mm:ssZUTC time(Z suffix)2025-03-15T10:00:00Zyyyy-MM-ddTHH:mm:ssLocal time(no Z, usestimezonefield)2025-03-15T10:00:00设置会议时间方式一本地时间 timezone字段推荐避免时区换算错误{ topic: Team Meeting, type: 2, start_time: 2025-03-15T10:00:00, timezone: America/Los_Angeles, duration: 60 }方式二直接用 UTC 时间带Z后缀无需timezone{ topic: Team Meeting, type: 2, start_time: 2025-03-15T17:00:00Z, duration: 60 }注意部分 Report API 只接受 UTC 格式。每次调用前务必查阅对应端点的参考文档确认接受的格式。在 meeting-lifecycle.md 的创建示例中同时出现了start_time: 2025-03-15T10:00:00Z与timezone: America/New_York的混用形态——这种写法在实操中可用但保持本地时间 timezone或纯 UTC二选一的风格能最大程度减少歧义。纯日期参数部分端点如录制列表使用YYYY-MM-DD纯日期格式GET /v2/users/me/recordings?from2025-01-01to2025-01-31这与 recordings.md 中记录的一致from/to为YYYY-MM-DD格式的起止日期。注意录制处理有延迟会议结束到录制可查询之间存在处理时间不要假设刚结束就能拉到——这是 recording-pipeline.md 明确列出的常见陷阱之一。Download URL动态生成、必须鉴权、必须跟随重定向API 响应与 Webhook 负载中的录制download_url是动态生成的需要认证才能访问且可能发生 301/302 重定向。认证方式在 Authorization 头中携带 Bearer token推荐curl -L -H Authorization: Bearer ACCESS_TOKEN \ https://zoom.us/rec/archive/download/xyz使用 Webhook 负载中的download_access_token用于 Webhook 触发的下载场景curl -L -H Authorization: Bearer DOWNLOAD_ACCESS_TOKEN \ https://zoom.us/rec/archive/download/xyz跟随重定向Download URL 可能返回 301/302 重定向必须始终跟随// Node.js — fetch follows redirects by default const response await fetch(downloadUrl, { headers: { Authorization: Bearer ${accessToken} }, redirect: follow }); const fileBuffer await response.arrayBuffer();# Python — requests follows redirects by default import requests response requests.get( download_url, headers{Authorization: fBearer {access_token}}, allow_redirectsTrue, streamTrue ) with open(recording.mp4, wb) as f: for chunk in response.iter_content(chunk_size8192): f.write(chunk)在 SKILL.md 的关键陷阱一节同样强调download_url需要 Bearer token 认证且要跟随重定向curl -L。recording-pipeline.md 进一步把不附加 Bearer token 就跟随download_url和不处理重定向响应列为最常见的两大坑。完整的Webhook 触发 → 拉取录制列表 → 鉴权下载 → 存入对象存储流水线可参考该文件。Personal Meeting IDPMI的特殊之处用户可以用自己的 PMI个人会议室 ID创建会议。API 响应中返回的是唯一的会议 ID但Webhook 事件仍然引用 PMI。因此对于基于 PMI 的会议向 API 端点传 ID 时应使用 PMI 本身。这类会议对应 meeting-lifecycle.md 中的type: 3Recurring with no fixed time即 PMI 会议。共享访问权限代他人操作的前置条件拥有 Schedule Privilege安排权限或基于角色的访问权限的用户可以代表其他用户执行操作。如果应用要访问非安装应用的用户的资源该用户必须已授权共享访问权限。未授权时的报错{ code: 403, message: authenticated user has not permitted access to the targeted resource }解决方式引导用户在其 Zoom 设置中开启共享访问权限。同样的错误模式code 3001 / 403也在 common-errors.md 的Shared Access Permissions场景中被记录先确认用户角色Admin/Owner再检查端点是否需要 admin 级 scope如meeting:write:admin而非meeting:write。邮箱显示规则外部参会者邮箱何时可见外部参会者的邮箱仅在满足以下任一条件时才被展示参会者在注册时填写了邮箱主持人通过日历集成、身份验证例外或分组讨论室breakout room分配提供了邮箱为网络研讨会主持人/参会者导入了 CSV这意味着在拉取参会者列表或报表时不要假设所有外部参会者的邮箱字段都有值——它受上述规则约束缺失是正常现象。高失败率风险与请求鉴权如果应用持续保持很高的错误/请求比率Zoom 可能直接禁用该应用。因此必须构建健壮的错误处理与优雅的重试逻辑。可参考 rate-limiting-strategy.md 中给出的三种策略指数退避 抖动处理 429、基于X-RateLimit-Remaining的主动限速、以及高并发场景的请求队列并注意限流按账号而非按应用统计会议/网络研讨会创建与更新每用户每天 100 次00:00 UTC 重置等硬约束。所有 API 请求都要求在Authorization头中携带 Bearer tokenAuthorization: Bearer {access_token}完整的认证实现参见 authentication-flows.md涵盖 S2S、User OAuth/PKCE、Device Code 四种流程的选型、令牌刷新与错误处理或直接使用zoom-oauth技能中的完整代码示例。实战速查把约定固化成编码习惯关注点正确姿势错误姿势Base URL从 OAuth 响应api_url推导追加/v2一律硬编码https://api.zoom.us/v2合规场景不满足me关键字User OAuth 用meS2S 用真实 userId/emailUser OAuth 传 userId报 4700S2S 传meUUID 编码以/开头或含//时双重编码仅单次encodeURIComponent报 404时间格式本地时间 timezone或纯 UTCZ无Z又不带timezone字段校验失败录制下载Bearer 鉴权 跟随重定向匿名访问或忽略 301/302共享访问确保目标用户已授权共享权限默认可访问他人资源报 403失败率指数退避、优雅重试、监控限流头无限重试、持续报错应用被禁用下一步阅读想跑通第一个请求meeting-lifecycle.md 提供了 Create → Update → Get → List → Delete 的完整 curl 与 Node.js 示例想拿令牌authentication-flows.md 或 zoom-oauth想处理限流与 429rate-limiting-strategy.md想自动下载录制recording-pipeline.md遇到错误common-errors.md 与 rest-api 的 RUNBOOK.md全部端点域参考rest-api 技能下的 references 目录39 个按 API 域划分的端点清单与官方 API Hub 的endpoints.json清单对齐【免费下载链接】knowledge-work-pluginsOpen source repository of plugins primarily intended for knowledge workers to use in Claude Cowork项目地址: https://gitcode.com/GitHub_Trending/kn/knowledge-work-plugins创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考