Skip to content
← Community packages

Call Zoom users, meetings, recordings, and webinars through OAuth or Server-to-Server secrets.

Browse files

  • Other
  • zoom
  • meetings
  • recordings
  • webinars
  • users
  • oauth
  • s2s
  • server-to-server
  • multi-account
  • dry-run
License
MIT
Published
August 22, 2026
Pinned commit
cd68dbc
Rating
No ratings yet
Forks
0
Stars
0
Adaptation effort

README

@kody/zoom

Official Zoom wordmark (Simple Icons path from Zoom brand) in white on Zoom Blue #0B5CFF. Paths are unmodified. Zoom® is a trademark of Zoom Communications, Inc. This package is not affiliated with or endorsed by Zoom.

Intent

Reusable Zoom REST helpers for Kody agents. One package covers bring-your-own user OAuth applications and Server-to-Server OAuth secrets for users, meetings, recordings, and webinars. Multi-account routing is zoom / zoom-* via integrationName or account — there are no hard-coded personal aliases or meeting IDs.

This listing is meant to be forked. After you fork, connect your Zoom account. Do not treat the live @kody/zoom package as a shared meeting calendar.

Share this listing at https://kody.codes/@kody/zoom

What this package does

After you connect Zoom to Kody, agents can:

  • Call the Zoom REST API with the correct saved credential (request, paginate)
  • Verify which Zoom identity will perform an action (get-current-user)
  • List and fetch users, meetings, recordings, and webinars
  • Preview or apply meeting and webinar writes (meetings/create, meetings/update, meetings/delete, and the matching webinar helpers)

It does not register a Zoom Marketplace app for you. There is no built-in Zoom platform app.

Auth lanes

LaneCredentialWhen to use
A. User OAuthSaved integration named zoom (or zoom-* via account / integrationName)Refreshable user OAuth. me works for the connected user.
B. Server-to-Server OAuthUser secrets zoomAccountId, zoomClientId, zoomS2sClientSecretAccount-level automations without a user consent screen. Pass auth: 's2s'.

Required hosts: api.zoom.us and zoom.us. Approve them in the account secrets UI.

auth: 's2s' or any S2S secret override selects the Server-to-Server lane and ignores integrationName.

Lane A: bring-your-own OAuth application

There is no built-in Zoom OAuth app. Create a General app, then connect it.

  1. Open Zoom Marketplace → Build an app and create a General OAuth app.
  2. Set the Redirect URL for OAuth exactly to https://kody.codes/connect/oauth (self-hosted Kody uses that deployment origin plus /connect/oauth).
  3. Add the user-level scopes listed below (or the subset you need).
  4. Copy the client ID and client secret. Never paste the secret into chat.
  5. Connect:
https://kody.codes/connect/oauth?provider=zoom&authorizeUrl=https%3A%2F%2Fzoom.us%2Foauth%2Fauthorize&tokenUrl=https%3A%2F%2Fzoom.us%2Foauth%2Ftoken&flow=confidential&scopes=user%3Aread%3Auser%20meeting%3Aread%3Alist_meetings%20meeting%3Aread%3Ameeting%20meeting%3Awrite%3Ameeting%20meeting%3Aupdate%3Ameeting%20meeting%3Adelete%3Ameeting%20cloud_recording%3Aread%3Alist_user_recordings%20cloud_recording%3Aread%3Arecording%20webinar%3Aread%3Alist_webinars%20webinar%3Aread%3Awebinar%20webinar%3Awrite%3Awebinar%20webinar%3Aupdate%3Awebinar%20webinar%3Adelete%3Awebinar&allowedHosts=api.zoom.us%2Czoom.us&apiBaseUrl=https%3A%2F%2Fapi.zoom.us%2Fv2&dashboardUrl=https%3A%2F%2Fmarketplace.zoom.us%2Fdevelop%2Fcreate

Decoded: authorize https://zoom.us/oauth/authorize, token https://zoom.us/oauth/token, flow=confidential, hosts api.zoom.us and zoom.us. Paste the client ID and secret into the Kody setup form, then authorize Zoom.

Default helper input: omit integrationName, account, and auth. The package uses OAuth integration zoom.

Reconnect later at https://kody.codes/connect/oauth?provider=zoom (or ?provider=<your-integration-name>).

Lane B: Server-to-Server OAuth secrets

  1. Open Zoom Marketplace → Build an app and create a Server-to-Server OAuth app.
  2. Add the admin scopes listed below, then Activate the app.
  3. Copy the account ID, client ID, and client secret from the app credentials page. Never paste them into chat.
  4. Save each secret and approve hosts api.zoom.us and zoom.us:
https://kody.codes/account/secrets/new?name=zoomAccountId&description=Zoom%20Server-to-Server%20account%20ID&allowedHosts=api.zoom.us%2Czoom.us&scope=user
https://kody.codes/account/secrets/new?name=zoomClientId&description=Zoom%20Server-to-Server%20client%20ID&allowedHosts=api.zoom.us%2Czoom.us&scope=user
https://kody.codes/account/secrets/new?name=zoomS2sClientSecret&description=Zoom%20Server-to-Server%20client%20secret&allowedHosts=api.zoom.us%2Czoom.us&scope=user

zoomS2sClientSecret is intentional. /connect/oauth may already store an OAuth app client secret as zoomClientSecret — do not reuse that name for Server-to-Server.

  1. Call helpers with auth: 's2s'.

Helpers mint a one-hour access token via grant_type=account_credentials and never return or log that token.

Scopes

User-level (Lane A):

ScopeNeeded for
user:read:user./get-current-user, ./users/get
meeting:read:list_meetings / meeting:read:meetingMeeting reads
meeting:write:meeting / meeting:update:meeting / meeting:delete:meetingMeeting writes
cloud_recording:read:list_user_recordings / cloud_recording:read:recordingRecording reads
webinar:read:list_webinars / webinar:read:webinarWebinar reads
webinar:write:webinar / webinar:update:webinar / webinar:delete:webinarWebinar writes

Server-to-Server (Lane B) uses the matching :admin scopes, plus user:read:list_users:admin for ./users/list.

If Zoom returns 401/403, helpers throw a message that names the next setup URL.

Multiple accounts

Every export accepts:

  • integrationName — exact saved OAuth name (zoom-work)
  • accountworkzoom-work / zoomAccountId-work / …; zoom-work used as-is; omitted → zoom
  • auth'oauth' or 's2s'
  • accountIdSecret / clientIdSecret / clientSecretSecret — S2S secret overrides

Connect extra OAuth accounts by changing provider in the connect URL:

https://kody.codes/connect/oauth?provider=zoom-work&authorizeUrl=https%3A%2F%2Fzoom.us%2Foauth%2Fauthorize&tokenUrl=https%3A%2F%2Fzoom.us%2Foauth%2Ftoken&flow=confidential&scopes=user%3Aread%3Auser%20meeting%3Aread%3Alist_meetings%20meeting%3Aread%3Ameeting&allowedHosts=api.zoom.us%2Czoom.us&apiBaseUrl=https%3A%2F%2Fapi.zoom.us%2Fv2

Do not hard-code a personal meeting ID or alias in forks of this package. Pass meetingId, webinarId, or a zoom.us/j/{id} / zoom.us/w/{id} URL at call time.

import getZoomCurrentUser from 'kody:@kody/zoom/get-current-user'

export default async function main() {
	return await getZoomCurrentUser({ integrationName: 'zoom-work' })
}

Mutations and dryRun

Create, update, and delete for meetings and webinars — and REST methods other than GET / HEAD / OPTIONS — require:

  • dryRun: true — validate and return a preview; Zoom is not written
  • confirm: true — perform the live write
import createZoomMeeting from 'kody:@kody/zoom/meetings/create'

export default async function main() {
	return await createZoomMeeting({
		topic: 'Example meeting',
		type: 2,
		start_time: '2030-01-15T17:00:00',
		duration: 30,
		timezone: 'UTC',
		dryRun: true,
	})
}

Do not create a live meeting unless the user confirmed the exact topic, time, and host.

Jobs

upcoming-meetings is declared and disabled by default. It lists upcoming meetings for me when an operator enables it after a successful smoke test.

Smoke tests

Run these from execute after connect (or after S2S host approval). Prefer a static kody:@kody/zoom import for the package overview.

Package overview (no credentials)

import describeZoom from 'kody:@kody/zoom'

export default async function main() {
	return await describeZoom()
}

A successful response includes publicUrl: 'https://kody.codes/@kody/zoom', the OAuth connect URL, and the three S2S secret URLs.

Mutation preview (no credentials)

import createZoomMeeting from 'kody:@kody/zoom/meetings/create'

export default async function main() {
	return await createZoomMeeting({
		topic: 'Example meeting',
		type: 2,
		start_time: '2030-01-15T17:00:00',
		duration: 30,
		timezone: 'UTC',
		dryRun: true,
	})
}

Identity (Lane A — OAuth)

import getZoomCurrentUser from 'kody:@kody/zoom/get-current-user'

export default async function main() {
	return await getZoomCurrentUser()
}

Identity (Lane B — Server-to-Server)

import getZoomCurrentUser from 'kody:@kody/zoom/get-current-user'

export default async function main() {
	return await getZoomCurrentUser({ auth: 's2s' })
}

Exports

  • kody:@kody/zoom — package overview and setup URLs
  • kody:@kody/zoom/accounts — OAuth vs Server-to-Server lane guidance
  • kody:@kody/zoom/get-current-user — identity smoke test
  • kody:@kody/zoom/paginatenext_page_token helper
  • kody:@kody/zoom/request — REST request helper
  • kody:@kody/zoom/types — shared TypeScript types
  • kody:@kody/zoom/smoke-test — local checks; pass live: true for /users/me
  • kody:@kody/zoom/users/list — list account users
  • kody:@kody/zoom/users/get — fetch one user
  • kody:@kody/zoom/meetings/list — list meetings
  • kody:@kody/zoom/meetings/get — fetch one meeting
  • kody:@kody/zoom/meetings/create — create a meeting (dryRun / confirm)
  • kody:@kody/zoom/meetings/update — update a meeting (dryRun / confirm)
  • kody:@kody/zoom/meetings/delete — delete a meeting (dryRun / confirm)
  • kody:@kody/zoom/recordings/list — list user recordings
  • kody:@kody/zoom/recordings/get — fetch meeting recordings
  • kody:@kody/zoom/webinars/list — list webinars
  • kody:@kody/zoom/webinars/get — fetch one webinar
  • kody:@kody/zoom/webinars/create — create a webinar (dryRun / confirm)
  • kody:@kody/zoom/webinars/update — update a webinar (dryRun / confirm)
  • kody:@kody/zoom/webinars/delete — delete a webinar (dryRun / confirm)

Meeting locators: { meetingId }, { meeting_id }, { id }, or { meetingUrl: 'https://zoom.us/j/11111111111' }. Webinar locators accept webinarId or https://zoom.us/w/{id}.

Examples

Upcoming meetings (default OAuth zoom):

import listZoomMeetings from 'kody:@kody/zoom/meetings/list'

export default async function main() {
	return await listZoomMeetings({ type: 'upcoming', page_size: 10 })
}

Recordings for a date window on a second account:

import listZoomRecordings from 'kody:@kody/zoom/recordings/list'

export default async function main() {
	return await listZoomRecordings({
		from: '2030-01-01',
		to: '2030-01-31',
		account: 'work',
	})
}

Unwrapped REST:

import zoomRequest from 'kody:@kody/zoom/request'

export default async function main() {
	return await zoomRequest({
		path: '/users/me/meetings',
		query: { type: 'upcoming', page_size: 5 },
	})
}

Troubleshooting

  • The redirect URI mismatch: callback must be exactly https://kody.codes/connect/oauth.
  • 401 Invalid access token with OAuth: reconnect at https://kody.codes/connect/oauth?provider=zoom.
  • 401 on Server-to-Server: the app must be activated; all three secrets must belong to the same app; approve zoom.us and api.zoom.us.
  • 400 / missing scope on webinars: the host needs a webinar license and the matching webinar scopes.
  • Writes rejected with a mutation-guard error: pass dryRun: true or confirm: true.

Docs

Report this listing

Log in to report this listing.