---
name: zoom-oauth-app
description: Creates or signs in to a Zoom developer-enabled account and registers a General (OAuth) app on the Zoom App Marketplace to obtain client ID and client secret — with the OAuth allow list, admin-managed vs user-managed choice, granular scopes, the separate development and production credential pairs, the deauthorization endpoint requirement, the private/beta install caps, and Marketplace review. Use when asked to get Zoom OAuth credentials, create a Zoom Marketplace app, choose between a General app and a Server-to-Server OAuth app, migrate a Zoom app from classic to granular scopes, rotate a Zoom client secret, or fix a Zoom error like `Invalid redirect`, `invalid_client`, or a customer whose Zoom connection stops refreshing. For Zoom Phone scopes specifically, read this file and then `zoom-phone-oauth-app`. For any other vendor's developer portal, use that vendor's skill instead.
---

# Zoom OAuth2 App Registration

Get a working Zoom OAuth2 client — a developer-enabled Zoom account, a **General app** on the Zoom App Marketplace,
an OAuth allow list, a scope set, and a client ID and secret — for a platform that connects many customers' Zoom
accounts.

Three things about this portal cost more than they look. **Every app has two credential pairs**, development and
production, and they are not interchangeable — half of all "invalid_client" reports are a development secret pointed
at production traffic. **The app's management type (admin-managed vs user-managed) decides which scopes exist at all**,
and changing it later resets the scope selection. And **an unreviewed app cannot serve customers**: a private app
tops out at 10 account-level installs, a beta app's sharing link lives four weeks at a time, and only a *published*
app — listed or unlisted, both of which go through Marketplace review — can be installed by the general public.

The fourth thing is the scope vocabulary. Zoom has two of them (classic and granular), they **cannot coexist in one
app**, and any newly created app gets granular. A scope list inherited from an older integration will not paste in.

## Inputs to collect before you start

Ask in one batch. Never invent.

| Input | Notes |
| --- | --- |
| **App name**, short/long description, company name, developer contact name and email | Contact details are required to activate the app |
| **Which Zoom account owns the app** | Needs owner, admin, or the developer role (§2) |
| **Admin-managed or user-managed?** | Decides the available scopes; expensive to change later (§3) |
| **Redirect URL + every OAuth allow-list entry** | One redirect per environment, plus the allow list (§4) |
| **Scope set** | What the connector actually calls, and which scopes are optional (§5) |
| **Deauthorization notification endpoint URL** | Required for production apps; you must delete user data on receipt (§7) |
| **Distribution intent** | Internal, private, beta, published-listed, published-unlisted (§7) |
| **Logo, Terms of Use, Privacy Policy, support and documentation URLs** | Required to publish (§7) |

## Quick Start

1. Confirm a **new app** is needed — existing connections are bound to the current client ID (§1).
2. Sign in to the Marketplace with an account that has developer permissions (§2).
3. **Develop → Build an app → General app**, then set the management type (§3).
4. Enter the redirect URL and fill the **OAuth allow list** (§4).
5. Select granular scopes and write a justification for each one — reviewers read these (§5).
6. Capture **both** credential pairs and note which is which; store the `api_url` rule (§6).
7. Decide distribution, register the deauthorization endpoint, and start review if customers are external (§7).
8. Verify with a **second** Zoom account, and force two consecutive refreshes (§8).
9. Hand the credentials over — never commit them (§9).

## Platform state (verified 2026-09-20 — re-verify before trusting)

- **The app types you can create today** are **General app** (user-authorizes-you OAuth, what a multi-tenant
  connector needs), **Server-to-Server OAuth** (one account's own data, `account_credentials` grant, no user
  interaction), plus SDK, Zoom Apps and Connector app variants of the same build flow. A General app is separately
  configured as **admin-managed** (account-level) or **user-managed** — that is a setting on the app, not a distinct
  app type.
- **JWT apps are gone.** New JWT apps stopped being creatable on **June 1, 2023**; Zoom deactivated all JWT apps on
  **September 8, 2023** (moved from September 1). If a runbook tells you to create a JWT app, the runbook is three
  years stale — the replacement is Server-to-Server OAuth or a General app.
- **Granular scopes shipped 2024-03-21 and are what any new app gets.** Zoom's own wording: "**Newly created apps**
  use granular scopes" — new build-flow apps, new Server-to-Server apps, and new apps on the legacy build flow.
  Apps created before that release use **classic scopes** and may migrate. **Classic and granular scopes do not
  exist in the same app**: an app on one vocabulary never sees the other in the build flow.
- **There is no published deadline for classic scopes.** Zoom's migration guide says existing apps "can continue to
  use classic scopes, but we encourage you to migrate them," and no changelog entry names a retirement date. Treat
  any specific sunset date you are told as unverified until someone shows you Zoom's own announcement — and treat
  the absence of a deadline as a reason to migrate on your schedule, not as permission to ignore it (§5).
- **Access tokens live one hour. Refresh tokens expire after 90 days and rotate on every refresh** — "You should
  always use the latest refresh token for the next refresh request."
- **Private and beta apps do not go through review, and are capped**: a private *account-level* app maxes out at
  **10** additions, a private *user-level* app at **100**, and a beta app's shared authorization URL runs **4 weeks**
  with at most two further 4-week extensions (§7).

If the Marketplace does not look like this, stop and report what you actually see rather than clicking on.

## 1. Decide: reuse the existing app, or register a new one

A new app means a **new client ID, and every existing customer connection is bound to the old one** — every customer
would have to re-authorize. Reuse the existing app for: adding an allow-list entry, adding or removing a scope,
rotating a compromised secret, migrating classic scopes to granular, or diagnosing an authorization failure.

Register a **new** app only when the user explicitly wants one: a replacement for a compromised app, a separate app
for a different product, or a deliberate move from a user-managed to an admin-managed posture (which is also a
re-authorization event for everyone). Say which path you are taking before you touch anything.

## 2. Account: sign in, and the developer permission

Sign in at `https://marketplace.zoom.us/`. Creating apps requires being the **account owner**, an **account admin**,
or a user assigned the **Zoom for developers** role — an admin grants it under **User Management → Roles → Role
Settings → Advanced features**, checking **View** and **Edit** for *Zoom for developers*.

**The Marketplace only shows the `Developer` link when it detects those permissions.** If the link is missing, that
is the diagnosis: it is a permissions problem on the Zoom account, not a browser or account-type problem. Do not hunt
for another route.

Hand control back to the user for anything only they can do: account signup and email verification, 2FA enrollment,
accepting the Marketplace Developer Agreement, or granting themselves the developer role. Do not retry a blocked
step in a loop.

**If this session has no browser automation** (the usual case for a CLI or cloud run), do not pretend to click. Hand
the user an exact, ordered click path with the literal values to paste (§4 allow list, §5 scope list), then continue
once they report back with the client ID.

## 3. Create the app, and the management-type decision

**Develop → Build an app → General app → Create.** Then, on **Basic Information**:

- **Select how the app is managed** — the one choice on this page that shapes everything downstream:
    - **Admin-managed**: account admins add and manage the app; depending on scope it reaches the data of all users
      on the account. This is what an admin-installs-once connector needs, and it is the only way to use `:admin`
      scopes.
    - **User-managed**: individual users add the app; it reaches only that user's authorized data.

  Zoom's own warning is worth repeating: "The app management type affects the features and scopes available to your
  app. If you change the app management type later on, make sure you reconfirm the selected features and scopes."
  In practice, flipping it mid-project means re-selecting scopes and re-testing — decide it now, from whether the
  customer's admin or the customer's individual users will click Allow.

- **App Credentials** — the build flow generates **two** pairs: development credentials for building and testing,
  production credentials for after you publish. They are different values. Label them the moment you copy them; a
  development secret used against production traffic fails at the token exchange with a generic client error and
  looks exactly like a typo.

**Server-to-Server OAuth is the other app type** and it is not an alternative for a multi-tenant connector. It
authenticates with **account ID + client ID + client secret** using the `account_credentials` grant, issues a
one-hour token with **no refresh token**, and the credentials belong to *one* Zoom account — so serving N customers
would mean N customers each creating their own app and handing you three secrets. It is the right answer only when
the user is integrating their *own* Zoom account. Account admins authorize which scopes a Server-to-Server app may
use, and the token stops working the moment the app is deactivated.

## 4. Redirect URL and the OAuth allow list

Two separate fields, and people fill in only the first:

- **OAuth redirect URL (required)** — where Zoom sends the authorization code.
- **OAuth allow lists (required)** — "any unique URLs that Zoom should allow as valid redirects for your OAuth
  flows." Accepts either the complete URL (`https://subdomain.domain.tld/path/to/oauth/callback`) or the base URL
  without path or query (`https://subdomain.domain.tld`).

For a platform that serves several data centers, put **every** callback host in the allow list. For Unified.to these
are one per data center; confirm the current list with the platform owner rather than assuming:

```
https://api.unified.to/oauth/code          # us (default)
https://api-eu.unified.to/oauth/code       # eu
https://api-au.unified.to/oauth/code       # au
https://api-dev.unified.to/oauth/code      # dev
```

Notes that decide whether a connect flow works:

- Two optional hardening toggles change matching: **Use Strict Mode URL** allows only redirects that match the valid
  redirect URLs exactly, and **Subdomain check** allows only redirects matching the subdomain of the valid URLs.
  Turning either on after customers are live can break hosts that were previously tolerated — change them
  deliberately, not as a tidy-up.
- The user-facing failure from the authorize endpoint reads `Invalid redirect: <redirectUri>`. It appears at connect
  time, not at save time, so nothing in the portal tells you the app is broken.
- **Loopback redirects (`http://127.0.0.1:{port}/...`) are for PKCE-enabled and native clients only**, and Zoom
  matches them ignoring *only* the port — scheme, host literal, path, query, fragment and user info must match
  exactly. A confidential server-side app must use HTTPS redirects; Zoom rejects loopback for it. Note it must be the
  numeric literal, never `localhost`.
- **Custom URL schemes are supported only for Meeting SDK apps** in the build flow. A desktop connector that wants
  `myapp://callback` needs a different design.

PKCE is available and optional here, not mandatory: turning on **Use Public Client OAuth** yields a separate **public
client ID** that needs no secret, and the confidential `client_id:client_secret` Basic flow keeps working alongside
it. If you enable public-client PKCE on an app you intend to publish, expect to justify it — Zoom's reviewers ask
whether you use the confidential flow, the public PKCE flow, or both.

## 5. Scopes

### Classic and granular

- **Classic scopes** are the older macro scopes (`user:read`, `phone:read:admin`, `meeting:write:admin` …), split
  into user-level, admin-level and Master-level. Admin-level scopes go to account-level apps, and **the user adding
  the app must be an account admin or owner** to be granted admin capabilities. Master-level scopes can only be
  granted to an account-level app by account owners.
- **Granular scopes** are `<service>:<action>:<data_claim>:<access>` — four actions (`read`, `write`, `update`,
  `delete`) where classic had two, and an access suffix that is empty (user), `:admin`, or `:master`. Example:
  `user:read:list_users:admin`.
- A given app has **one** vocabulary. New apps get granular. Migration is a button on the **Scope** page of the
  **Development** tab, then the same button on **Production**; Zoom infers the granular equivalents from the APIs
  and events your classic scopes reached, and you prune the list before testing. Re-review is **not** required to
  migrate or to *reduce* scopes; adding a new granular scope **does** require re-submission before it takes effect
  in production.
- Migration is not a token event for people already connected: existing users keep working, their tokens keep
  returning **classic** values in the `scope` field, and they are not asked to re-authorize. New authorizations — and
  any existing user who re-authorizes — get granular values, and the old token is invalidated. **If your client
  parses the `scope` string, fix that before migrating**; it is the one documented breaking change.

### Required vs optional, and how the authorize URL must match

Granular scopes can be marked **Optional** in the build flow. Which scopes the consent screen demands then depends on
how you build the authorize URL:

- **Basic query** — send no `scope` parameter and the user sees the app's configured required and optional scopes.
- **Advanced query** — send `scope=` (required for this attempt) and `optional_scope=` (optional for this attempt)
  to override the build-flow defaults per authorization. Every value must already be added to the app.
- **`include_granted_scopes`** returns a token carrying the user's previously authorized scopes as well, which is how
  you add a capability later without dropping what you already had.

A connector that sends no `scope` parameter is using the basic query, so **the app's configured scope list *is* the
consent screen**. Adding a scope in the portal silently widens what every new customer is asked for.

### Writing scope justifications

Each scope takes a **Scope Description** explaining why the app needs it. This is not busywork: "When you add a
scope, you are actually submitting a request to the Zoom Security Review team… If the Zoom Security Review team
determines the requested scope is not necessary for your app, they may reject the scope request." The Marketplace
Security Review explicitly "verifies that the app only requests the minimum necessary OAuth scopes" and asks
developers to remove unused ones. Request the narrow set, and write one concrete sentence per scope naming the
feature it powers.

### Product fact — what the Zoom connector asks for

> **As of 2026-09-20, Unified.to's Zoom connector requests**, by object and direction:
>
> - **Users (read):** `user:read:user:admin`, `user:read:list_users:admin` — **(write):** `user:write:admin`
> - **Groups (read):** `group:read:admin`, `group:read:list_groups:admin` — **(write):** `group:write:admin`
> - **Meetings (read):** `meeting:read:list_meetings` — **(write):** `meeting:write:meeting`,
>   `meeting:update:meeting`, `meeting:delete:meeting`
> - **Webinars (read):** `webinar:read:list_webinars` — **(write):** `webinar:write:webinar`,
>   `webinar:update:webinar`, `webinar:delete:webinar`, plus the registrant and panelist write/update/delete scopes
> - **Team Chat channels (read):** `team_chat:read:list_user_channels`, `team_chat:read:user_channel`,
>   `team_chat:read:list_members`
> - **Team Chat messages (read):** `team_chat:read:list_user_messages`, `team_chat:read:user_message` —
>   **(write):** `team_chat:write:user_message`, `team_chat:update:user_message`, `team_chat:delete:user_message`
> - **Contacts (read):** `team_chat:read:list_contacts`, `team_chat:read:contact`
> - **Identity / login only:** `user_profile`, `user_info:read`, `user:read`
>
> Two things to raise with the connector's owner before you configure an app, rather than assuming either way:
>
> 1. **The set mixes vocabularies.** Most entries are granular (four parts), but the identity/login set and the user
>    and group write entries are classic-shaped (`user:read`, `user:write:admin`, `group:write:admin`). Since an app
>    is on *one* vocabulary and a newly created app is granular, those classic names will not be selectable in the
>    build flow, and an advanced authorize query carrying them against a granular app will not match.
> 2. **Most read scopes carry `:admin`**, so the app must be **admin-managed** and the person clicking Allow must be
>    an account admin or owner. A user-managed configuration will authorize and then 403.
>
> The connector authorizes without sending a `scope` parameter — so the consent screen is whatever the app is
> configured with — exchanges the code as a form POST with Basic auth, and stores the region base URL that Zoom
> returns with the token (see §6).
>
> Scope sets change. This note is dated, not live; confirm with the connector's owner before submitting anything.

## 6. Capture the credentials

From the app's **Basic Information → App Credentials** page. Capture, per environment:

- **Client ID** and **client secret** — **development** and **production** are separate pairs; record which is which
- **Public client ID**, if public-client OAuth is enabled (no secret; PKCE only)
- Endpoints: `https://zoom.us/oauth/authorize` and `https://zoom.us/oauth/token`
- The **Secret Token** for webhook verification, if you subscribe to events
- For a Server-to-Server app only: the **account ID** as well

The secret is re-readable in the portal by anyone with developer permissions on that Zoom account, and regenerable.
Regenerating invalidates the old secret: existing access tokens run out their hour, but every code exchange and
refresh fails until the new value is deployed. Never rotate without explicit go-ahead and a cutover plan.

**The `api_url` rule.** The token response carries an `api_url` naming the region that serves that user — Australia,
Canada, the EU, India, Saudi Arabia, Singapore, the UK, the US, or a vanity host. Zoom says you may keep calling the
global `https://api.zoom.us` regardless, but storing `api_url` per connection and calling the customer's region is
the documented pattern and the faster one. Remember the `/v2/` path segment: the returned value is a host, not a
base path.

**Token lifetimes, stated once.** Access token: one hour (`expires_in` 3600, sometimes 3599 — do not parse it as a
constant). Refresh token: **90 days, and rotated on every refresh**. Two consequences that produce most "it stopped
working" reports: a client that keeps replaying the original refresh token fails on the *second* refresh, and a
connection that sits idle for 90 days is dead and needs the customer back in the authorize flow. Server-to-Server
apps have no refresh token at all — you request a fresh one-hour token whenever you need it.

Report the secret once so the user can paste it into their secret store, say plainly that it is now in the
transcript and can be regenerated, then move on.

## 7. Limits, review, and listing

**Distribution is the decision that gates everything else.** The types, and who can install:

| Type | Review | Who can install | Distribution |
| --- | --- | --- | --- |
| **Internal** | No standard Marketplace review | Users on your own Zoom account only | Published but restricted to your account |
| **Private** | None | Users on your own Zoom account only | Shared authorization URL; **max 10 additions account-level, 100 user-level** |
| **Beta** | Sharing outside your account needs Zoom's approval | Limited external testers | Authorization URL, **4 weeks**, plus at most two 4-week extensions |
| **Published (Listed)** | Full review | Anyone | Discoverable in the Marketplace |
| **Published (Unlisted)** | Full review, plus complete listing metadata | Anyone with the link | Not shown in Marketplace search |

**A multi-tenant connector needs a published app** — listed or unlisted. Private and beta are testing postures with
hard caps, and the beta authorization URL stops admitting *new* users when it expires (people who already added the
app keep working). Requests to share a beta URL outside your account get an answer from the review team in **3–4
business days**. Unlisted still fails review without full listing metadata, because that metadata is shown in the
active-apps notification and on users' added-apps page.

**What review actually covers**, in order: submission completeness (metadata, technical design, security
information); branding and name distinctness; functionality, usability and compliance (they install, configure and
use the integration); then the Security Review — a technical design review, an **OAuth scope evaluation** against
least privilege, and security testing against the OWASP Top 10 including web application scanning, manual misuse
testing and vulnerable-library checks. Sharing a beta URL externally additionally wants "supporting evidence of your
app's security practices" — Zoom names a Secure SDLC, SAST and/or DAST scanning, and a third-party penetration test.
**Compliance attestations, penetration-test scheduling and legal commitments are decisions for a human**; collect
what exists, do not draft claims.

Publishing also obliges you to provide, at minimum, **Terms of Use, a Privacy Policy, self-serve documentation, and a
support URL**.

**The deauthorization endpoint is not optional for a production app.** When a user removes the app, Zoom POSTs a
deauthorization event to the notification endpoint URL you registered on the App Listing page, and "apps must delete
all associated user data" on receipt. Zoom recommends verifying those requests with the supported webhook
verification methods — an unsecured endpoint is a denial-of-service target. Decide who owns that endpoint and the
deletion it triggers *before* submitting; it is a product commitment, not a form field.

**Rate limits are per Zoom account, shared across every app installed on it, and set by the customer's plan** — so a
customer on Free gets 4/second and 6000/day on Light APIs while Business+ gets 80/second, and your connector is
competing with everything else that account has installed. Categories are Light, Medium, Heavy and Resource-intensive
(each endpoint's docs name its label), enforced per second and/or per day; Pro and Business+ add combined daily caps
of 30,000 and 60,000 requests for heavy plus resource-intensive calls. Over-limit is **HTTP 429** — back off and
retry, never re-authorize. Separately: **Zoom may disable an app with a high error-to-request ratio**, which makes
sloppy retry logic an existential risk rather than a nuisance.

## 8. Verify end-to-end

Authorizing on the Zoom account that owns the app proves little. Test the path a customer takes:

1. Authorize from a **second Zoom account** through your platform's real connect flow, with the role customers will
   use — an admin if the app is admin-managed, a plain user if it is user-managed.
2. Confirm the token response carries a **refresh token** and an **`api_url`**, and that API calls go to that host
   with `/v2/` in the path.
3. Force a **refresh**, then force a **second** refresh using the token returned by the first. That is what catches a
   client that ignores rotation, and it is the single most valuable check on this platform.
4. Make one real read call and confirm the granted scopes match what you requested — an optional scope the user
   declined fails later at the API, not at consent.
5. Confirm the deauthorization endpoint receives an event when you remove the app from the test account.

| Symptom | Cause |
| --- | --- |
| `Invalid redirect: <uri>` at authorize | Redirect not in the OAuth allow list, or Strict Mode / Subdomain check now excludes it (§4) |
| Loopback redirect rejected | App is a confidential client without PKCE, or the URI used `localhost` instead of `127.0.0.1` (§4) |
| Token exchange fails with a generic client error | Development credentials used against production, or vice versa (§3) |
| Second refresh fails, first one worked | Client is replaying the original refresh token instead of the rotated one (§6) |
| Connection dies after a quiet quarter | 90-day refresh token expiry — the customer must re-authorize (§6) |
| Auth succeeds, admin endpoints 403 | App is user-managed, or the installer is not an account admin/owner (§3, §5) |
| 403 `authenticated user has not permitted access to the targeted resource` | Acting on another user's data without that user's shared access permissions (§8 note below) |
| Scope not selectable in the build flow | Classic name on a granular app, or vice versa — they never coexist (§5) |
| `429` on a customer who was fine yesterday | Account-wide, plan-based rate limit shared with their other apps (§7) |
| New customers cannot add the app; existing ones fine | Private/beta install cap reached, or the beta authorization URL expired (§7) |

**Shared access permissions**, worth knowing before you chase a 403: a Zoom user may hold privileges over other
users (scheduling privilege, a role), and separately chooses whether your app may use them. If the app touches
another user's data and the adding user either lacks that privilege or has not granted your app shared access, Zoom
returns 403 with "authenticated user has not permitted access to the targeted resource." **Your app cannot detect
whether shared access was granted** — there is no flag to read. The only remedy is to point the customer at Zoom's
help article on allowing apps access to shared access permissions.

## 9. Hand off — never commit the secret

- **Do not** write the client secret (or the webhook secret token) into source control, a test, a fixture, a
  committed `.env`, a ticket, a PR body, or a chat channel. Values go to the user, for the secret store or console.
- If a code change is needed (an allow-list host, a scope, refresh-token rotation handling), keep it secret-free and
  say what the human must set out of band.
- Close with: app name and type (General app) and management type; which Zoom account owns it; **both** client IDs
  labelled development and production; where each secret was delivered; the authorize and token endpoints; the exact
  scope strings and which are optional; the `api_url` handling; the distribution choice and review status; who owns
  the deauthorization endpoint; and anything left for the user to do.

## Stop and ask

Hand back to a human rather than guessing when: the client does not store **rotated** refresh tokens (report it —
the app will register fine and fail on the second refresh); nobody has decided **admin-managed vs user-managed**;
nobody owns the **deauthorization endpoint** and the data deletion behind it; the app needs to serve external
customers and nobody has committed to **Marketplace review**; a review form asks for compliance, penetration-test,
legal or volume claims; someone proposes **migrating classic scopes to granular** on a live app whose client reads
the `scope` string; someone wants to enable **Strict Mode** or **Subdomain check** on an app with live customers; or
the Marketplace does not match the **Platform state** section above.

## References

Official Zoom docs only. Every URL verified to return 200 on 2026-09-20.

- Zoom App Marketplace — https://marketplace.zoom.us/
- Create an OAuth app (General app build flow) — https://developers.zoom.us/docs/integrations/create/
- OAuth 2.0: tokens, refresh, revoke, deauthorization, PKCE, loopback — https://developers.zoom.us/docs/integrations/oauth/
- OAuth scopes overview (classic vs granular, optional scopes, authorize queries) — https://developers.zoom.us/docs/integrations/oauth-scopes-overview/
- Granular scopes reference — https://developers.zoom.us/docs/integrations/oauth-scopes-granular/
- Classic scopes reference — https://developers.zoom.us/docs/integrations/oauth-scopes/
- Migrate from classic to granular scopes — https://developers.zoom.us/docs/integrations/migrate/
- Granular and optional scopes released (changelog, 2024-03-21) — https://developers.zoom.us/changelog/platform/granular-and-optional-scopes-announcement/
- Server-to-Server OAuth — https://developers.zoom.us/docs/internal-apps/s2s-oauth/
- Create a Server-to-Server OAuth app — https://developers.zoom.us/docs/internal-apps/create/
- Before you build: app types, audience, discoverability — https://developers.zoom.us/docs/build-flow/before-you-build/
- App distribution — https://developers.zoom.us/docs/distribute/
- App review process — https://developers.zoom.us/docs/distribute/app-review-process/
- Sharing private and beta apps (install caps, URL expiry) — https://developers.zoom.us/docs/distribute/sharing-private-and-beta-apps/
- Security requirements and technical design — https://developers.zoom.us/docs/distribute/security-requirements/
- Rate limits — https://developers.zoom.us/docs/api/rate-limits/
- Using Zoom APIs (regional base URLs, shared access permissions, `me`) — https://developers.zoom.us/docs/api/using-zoom-apis/
- Webhooks and event verification — https://developers.zoom.us/docs/api/webhooks/
- Marketplace events, including app deauthorized — https://developers.zoom.us/docs/api/marketplace/events/
- JWT app type deprecation (changelog) — https://developers.zoom.us/changelog/platform/jwt-app-type-deprecation/
- Deadline change for JWT app type deprecation (changelog) — https://developers.zoom.us/changelog/platform/deadline-change-for-jwt-app-type-deprecation/
