Generated banner for Build Zoom Rest API App
Generated logo for Build Zoom Rest API App

Build Zoom Rest API App

OpenAIOfficialCreated by ZoomDistributed by OpenAI

  • Skill
  • Tool integration
  • Software development
  • MIT
  • Medium risk

From the author

Use when calling REST APIs.
— the description by Zoom

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides a structured approach to interacting with the Zoom REST API. It offers guidance on selecting appropriate authentication flows, managing resources such as meetings and users, and implementing robust API interaction patterns like pagination and rate-limit handling. It includes reference documentation for various Zoom product areas, including account management and AI Companion services, to assist developers in building API-integrated applications.

What it does

  • Provides endpoint inventories for Zoom API product areas.
  • Outlines authentication requirements for user-level, account-level, and server-to-server workflows.
  • Defines patterns for implementing API wrappers with pagination and retry logic.
  • Offers guidance on webhook processing, including signature verification.
  • Assists in troubleshooting common API issues like scope mismatches and rate limits.

When to use it

  • When developing applications that need to programmatically manage Zoom meetings, users, or recordings.
  • When integrating Zoom account settings or dashboard metrics into custom software.
  • When setting up event-driven workflows using Zoom webhooks.
  • When debugging authentication or scope-related errors during Zoom API integration.

Advantages

  • Centralizes access to official Zoom API endpoint documentation.
  • Standardizes the implementation of API interaction patterns.
  • Clarifies the selection of correct authentication scopes for different API operations.
  • Provides a clear methodology for handling API rate limits and resource management.

Usage examples

“How do I list meeting participants using the Zoom API?”
The agent provides the correct endpoint path from the dashboard references and explains the necessary OAuth scopes and pagination requirements.
“What authentication should I use for an account-level Zoom integration?”
The agent guides you through the account-level OAuth flow requirements as defined in the authentication documentation.

Install

  1. Download the skill as a ZIP:

    https://autogineer.com/agent-skills/openai/build-zoom-rest-api-app/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip build-zoom-rest-api-app.zip -d .agents/skills

Documentation by Zoom

Build Zoom REST API App

Use this skill when the task needs deterministic Zoom API calls or resource management from application code.

Workflow

  1. Define the resource and actor: user, meeting, webinar, recording, docs, chat, phone, account, or admin-level workflow.

  2. Select the endpoint and required scopes from the reference files before coding.

  3. Confirm auth fit: user-level OAuth for user-owned resources, account-level OAuth for admin workflows, and only use server-to-server OAuth where the target API documents support for it.

  4. Implement narrow API wrappers with explicit pagination, retry, idempotency, and rate-limit handling.

  5. Treat webhook processing as a separate event-ingestion path with signature verification and replay protection.

  6. Debug by checking token audience, missing scopes, resource ownership, account settings, API enablement, and rate-limit headers.

References

Files

references/authentication.md

Authentication Guide

Comprehensive guide to Zoom API authentication methods: OAuth 2.0, Server-to-Server OAuth, and JWT (legacy).

Overview

Zoom APIs support multiple authentication methods depending on your use case:

MethodUse CaseToken Lifetime
User OAuth 2.0Act on behalf of users1 hour (refresh: 15 years)
Server-to-Server OAuthBackend automation1 hour
JWT (Deprecated)Legacy integrationsCustom

For backend services that don't need user interaction.

Setup
  1. Go to Zoom App Marketplace

  2. Click Develop → Build App

  3. Select Server-to-Server OAuth

  4. Note your credentials:

    • Account ID

    • Client ID

    • Client Secret

Get Access Token
curl -X POST "https://zoom.us/oauth/token" \
  -H "Authorization: Basic $(echo -n '{clientId}:{clientSecret}' | base64)" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=account_credentials&account_id={accountId}"
Response
{
  "access_token": "eyJhbGciOiJIUzI1NiJ9...",
  "token_type": "bearer",
  "expires_in": 3600,
  "scope": "meeting:read meeting:write user:read"
}
Code Example (Node.js)
const axios = require('axios');

class ZoomAuth {
  constructor(accountId, clientId, clientSecret) {
    this.accountId = accountId;
    this.clientId = clientId;
    this.clientSecret = clientSecret;
    this.token = null;
    this.tokenExpiry = null;
  }

  async getAccessToken() {
    // Return cached token if still valid
    if (this.token && this.tokenExpiry > Date.now() + 60000) {
      return this.token;
    }

    const credentials = Buffer.from(
      `${this.clientId}:${this.clientSecret}`
    ).toString('base64');

    const response = await axios.post(
      'https://zoom.us/oauth/token',
      `grant_type=account_credentials&account_id=${this.accountId}`,
      {
        headers: {
          'Authorization': `Basic ${credentials}`,
          'Content-Type': 'application/x-www-form-urlencoded'
        }
      }
    );

    this.token = response.data.access_token;
    this.tokenExpiry = Date.now() + (response.data.expires_in * 1000);

    return this.token;
  }

  async apiRequest(method, endpoint, data = null) {
    const token = await this.getAccessToken();

    const config = {
      method,
      url: `https://api.zoom.us/v2${endpoint}`,
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      }
    };

    if (data) {
      config.data = data;
    }

    return axios(config);
  }
}

// Usage
const zoom = new ZoomAuth(
  process.env.ZOOM_ACCOUNT_ID,
  process.env.ZOOM_CLIENT_ID,
  process.env.ZOOM_CLIENT_SECRET
);

const users = await zoom.apiRequest('GET', '/users');

User OAuth 2.0 (For User Actions)

For applications that act on behalf of individual users.

OAuth Flow
1. User clicks "Connect to Zoom"
       ↓
2. Redirect to Zoom authorization URL
       ↓
3. User grants permission
       ↓
4. Zoom redirects to your callback with code
       ↓
5. Exchange code for access token
       ↓
6. Use access token for API calls
       ↓
7. Refresh token when expired
Step 1: Create OAuth App
  1. Go to Zoom App Marketplace

  2. Click Develop → Build App

  3. Select OAuth

  4. Configure:

    • Redirect URI(s)

    • Required scopes

    • App information

Step 2: Authorization URL

Redirect users to:

https://zoom.us/oauth/authorize?response_type=code&client_id={clientId}&redirect_uri={redirectUri}&state={state}
ParameterDescription
response_typeAlways code
client_idYour OAuth app client ID
redirect_uriMust match registered URI
stateRandom string for CSRF protection
Step 3: Handle Callback

User is redirected to your callback URL:

https://yourapp.com/callback?code=AUTH_CODE&state=STATE
Step 4: Exchange Code for Token
curl -X POST "https://zoom.us/oauth/token" \
  -H "Authorization: Basic $(echo -n '{clientId}:{clientSecret}' | base64)" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code&code={authCode}&redirect_uri={redirectUri}"
Response
{
  "access_token": "eyJhbGciOiJIUzI1NiJ9...",
  "token_type": "bearer",
  "refresh_token": "eyJhbGciOiJIUzI1NiJ9...",
  "expires_in": 3600,
  "scope": "meeting:read meeting:write user:read"
}
Step 5: Refresh Token
curl -X POST "https://zoom.us/oauth/token" \
  -H "Authorization: Basic $(echo -n '{clientId}:{clientSecret}' | base64)" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token&refresh_token={refreshToken}"
Complete OAuth Example (Express.js)
const express = require('express');
const axios = require('axios');
const crypto = require('crypto');

const app = express();

const ZOOM_CLIENT_ID = process.env.ZOOM_CLIENT_ID;
const ZOOM_CLIENT_SECRET = process.env.ZOOM_CLIENT_SECRET;
const REDIRECT_URI = 'https://yourapp.com/auth/zoom/callback';

// Step 2: Initiate OAuth
app.get('/auth/zoom', (req, res) => {
  const state = crypto.randomBytes(16).toString('hex');
  req.session.oauthState = state;

  const authUrl = new URL('https://zoom.us/oauth/authorize');
  authUrl.searchParams.set('response_type', 'code');
  authUrl.searchParams.set('client_id', ZOOM_CLIENT_ID);
  authUrl.searchParams.set('redirect_uri', REDIRECT_URI);
  authUrl.searchParams.set('state', state);

  res.redirect(authUrl.toString());
});

// Step 3 & 4: Handle callback and exchange code
app.get('/auth/zoom/callback', async (req, res) => {
  const { code, state } = req.query;

  // Verify state
  if (state !== req.session.oauthState) {
    return res.status(400).send('Invalid state');
  }

  try {
    // Exchange code for token
    const credentials = Buffer.from(
      `${ZOOM_CLIENT_ID}:${ZOOM_CLIENT_SECRET}`
    ).toString('base64');

    const tokenResponse = await axios.post(
      'https://zoom.us/oauth/token',
      `grant_type=authorization_code&code=${code}&redirect_uri=${REDIRECT_URI}`,
      {
        headers: {
          'Authorization': `Basic ${credentials}`,
          'Content-Type': 'application/x-www-form-urlencoded'
        }
      }
    );

    const { access_token, refresh_token, expires_in } = tokenResponse.data;

    // Store tokens securely (database)
    await storeTokens(req.user.id, {
      accessToken: access_token,
      refreshToken: refresh_token,
      expiresAt: Date.now() + (expires_in * 1000)
    });

    res.redirect('/dashboard');
  } catch (error) {
    console.error('OAuth error:', error.response?.data);
    res.status(500).send('Authentication failed');
  }
});

// Helper: Get valid access token (refresh if needed)
async function getValidToken(userId) {
  const tokens = await getStoredTokens(userId);

  // Check if token is expired (with 1 min buffer)
  if (tokens.expiresAt < Date.now() + 60000) {
    const credentials = Buffer.from(
      `${ZOOM_CLIENT_ID}:${ZOOM_CLIENT_SECRET}`
    ).toString('base64');

    const response = await axios.post(
      'https://zoom.us/oauth/token',
      `grant_type=refresh_token&refresh_token=${tokens.refreshToken}`,
      {
        headers: {
          'Authorization': `Basic ${credentials}`,
          'Content-Type': 'application/x-www-form-urlencoded'
        }
      }
    );

    await storeTokens(userId, {
      accessToken: response.data.access_token,
      refreshToken: response.data.refresh_token,
      expiresAt: Date.now() + (response.data.expires_in * 1000)
    });

    return response.data.access_token;
  }

  return tokens.accessToken;
}

Scopes

Common Scopes
ScopeDescription
user:readRead user profile
user:writeUpdate user profile
meeting:readRead meeting data
meeting:writeCreate/update meetings
recording:readAccess recordings
recording:writeManage recordings
phone:readRead Zoom Phone data
phone:writeManage Zoom Phone
Admin Scopes
ScopeDescription
user:read:adminRead all users
user:write:adminManage all users
meeting:read:adminRead all meetings
account:read:adminRead account settings
Scope Selection

Request only scopes you need:

  • More scopes = more user friction

  • Less scopes = better approval chance

  • Add scopes incrementally as features grow

Token Storage Best Practices

// DO: Encrypt tokens at rest
const encryptedToken = encrypt(accessToken, encryptionKey);
await db.tokens.save({ userId, encryptedToken });

// DO: Use secure, httpOnly cookies for web apps
res.cookie('zoom_token', accessToken, {
  httpOnly: true,
  secure: true,
  sameSite: 'strict',
  maxAge: 3600000
});

// DON'T: Store tokens in localStorage
localStorage.setItem('zoom_token', token); // BAD

// DON'T: Log tokens
console.log('Token:', accessToken); // BAD

Error Handling

Common OAuth Errors
ErrorCauseSolution
invalid_grantExpired/used codeRestart OAuth flow
invalid_clientWrong credentialsCheck client ID/secret
invalid_scopeUnauthorized scopeRequest only approved scopes
access_deniedUser denied permissionHandle gracefully
Token Refresh Errors
try {
  const newToken = await refreshToken(refreshToken);
} catch (error) {
  if (error.response?.data?.error === 'invalid_grant') {
    // Refresh token expired or revoked
    // User needs to re-authorize
    redirectToOAuth();
  }
}

Webhook Verification

For webhook security, verify the request signature:

const crypto = require('crypto');

function verifyWebhook(req, secret) {
  const message = `v0:${req.headers['x-zm-request-timestamp']}:${JSON.stringify(req.body)}`;
  const signature = crypto
    .createHmac('sha256', secret)
    .update(message)
    .digest('hex');

  const expected = `v0=${signature}`;
  return req.headers['x-zm-signature'] === expected;
}

app.post('/webhooks/zoom', (req, res) => {
  if (!verifyWebhook(req, process.env.ZOOM_WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }

  // Process webhook
  const event = req.body;
  // ...
});

Migration from JWT (Deprecated)

JWT apps are deprecated. Migrate to Server-to-Server OAuth:

  1. Create new Server-to-Server OAuth app

  2. Request same scopes

  3. Update code to use OAuth token endpoint

  4. Test thoroughly

  5. Delete JWT app

// OLD (JWT - Deprecated)
const token = jwt.sign(payload, apiSecret);

// NEW (Server-to-Server OAuth)
const token = await getServerToServerToken();

Resources

Requirements and compatibility

Written for: OpenAI Codex

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Security

What our scanner noticed· Medium risk

  • Possible data-exfiltration endpoints
  • Instructions that use the network

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
Zoom
Publisher
OpenAI (Official)
Distributed by
OpenAI
Source
openai/plugins · plugins/zoom/skills/rest-api · commit d420d13
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from plugins/zoom/LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Zoom or OpenAI.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download build-zoom-rest-api-app.zip