Skip to content
Belief Engines
Esc
navigateopen⌘Jpreview
On this page

Zoom API

Server-to-Server OAuth, the endpoints and scopes, and the four gotchas that will bite.

Zoom API

One Server-to-Server OAuth app, created by the account owner. No user consent flows, no refresh tokens, whole-account reach with :admin scopes. The token is grant_type=account_credentials with a 1-hour TTL, and no marketplace review is needed for a private app.

Five employees, one internal script, an admin who can flip account settings. User OAuth buys nothing and costs five authorization flows.

Endpoints and scopes

Purpose Call Scope
Enumerate GET /meetings/meeting_summaries meeting:read:list_summaries:admin
Fetch GET /meetings/<meeting-uuid>/transcript cloud_recording:read:meeting_transcript:admin
Summary body GET /meetings/<id>/meeting_summary meeting:read:summary:admin
Fallback enum GET /report/users/<user-id>/meetings report:read:user:admin
Occurrences GET /past_meetings/<id>/instances meeting:read:list_past_instances [UNVERIFIED]
Participants GET /past_meetings/<meeting-uuid>/participants report: family [UNVERIFIED]

The enumerators take from= and to=. Read the exact granular strings off the app’s scope picker — the two rows marked above were not read from a primary source.

Step one of the build: create the app as the account owner and screenshot the scope list. A 2026 dev forum thread reported meeting:read:list_summaries:admin could not be added to an S2S app; it resolved as a role permission on the creator’s user, not an S2S limit [UNVERIFIED]. That scope is the enumerator for the whole design. Fallback is in Pipeline.

Poll, don’t listen

meeting.aic_transcript_completed is the correct event and we still do not use it.

  WEBHOOK                              POLL, 7-day overlapping window
  ─────────────────────────            ─────────────────────────────
  3 failed deliveries and Zoom         Mac asleep, laptop shut,
  STOPS SENDING THE EVENT              script crashed — the next
  until a human re-validates           run catches all of it,
  in the console.                      silently.
  Silent, unbounded data loss.         Self-healing.
  Needs a public endpoint, HMAC
  verification, a url_validation
  handshake.

Retries go +5 min, +20 min, +60 min, then permanent mute. For five people with no on-call rota, “heals itself silently” beats “fails silently”.

Latency cost: next morning instead of an hour after the call. Zoom publishes no processing SLA, and third-party observation puts it near 2× meeting duration, occasionally 24 hours. The difference is mostly imaginary.

Gotchas

  • UUID is the primary key, not meeting ID. A recurring meeting reuses one ID across every occurrence. The UUID is per-occurrence.
  • Double URL-encode UUIDs containing /. They are base64, so /, + and = all appear. Single-encoding gives a 500 or a 3001.
  • /transcript wants the past-meeting instance UUID, not the scheduled meeting ID. Wrong one gives error 3322, “This meeting transcript does not exist”. Zoom’s docs are wrong on this and a staffer said so.
  • Rate limits are a non-issue at five meetings a day. Skip the backoff engineering. Just do not loop.

Last updated on August 27, 2026

Was this page helpful?