Zoom Credentials

Store and manage Zoom SDK credentials and OAuth tokens securely with the v2 Credentials API

Zoom Credentials API

The v2 Credentials API provides secure storage for your Zoom app credentials and OAuth tokens. Instead of passing SDK credentials with every bot request, you can store them once and reference them by ID.

Overview

The /v2/zoom-credentials endpoint lets you:

  • Store Zoom app credentials (SDK client ID and secret)
  • Exchange OAuth authorization codes for tokens
  • Manage multiple credentials for different Zoom users
  • Track credential health with state and error tracking

All credentials are encrypted at rest using AES-256-GCM. Secrets and tokens are never returned in API responses.

Credential Types

App-Only Credentials

Store your Zoom app's SDK credentials. Use these when your bots only join meetings within your own Zoom organization (internal meetings).

What's stored:

  • Client ID (SDK Key)
  • Client Secret (SDK Secret)

Use case: Recording your team's meetings without OBF tokens.

User Credentials

Store OAuth tokens for a specific Zoom user who authorized your app. Use these for OBF (On Behalf Of) token support when joining external meetings.

What's stored:

  • Client ID and Secret
  • Access token (encrypted)
  • Refresh token (encrypted)
  • Zoom user ID and account ID
  • Zoom email and display name (captured from Zoom's /users/me API at OAuth time, requires the user:read:user scope)
  • Granted scopes
  • Optional extra JSON object you supply to tag the credential (e.g. internal user ID, environment)

Use case: Building a product where customers authorize your bot to join their meetings.

Creating Credentials

App-Only Credentials

Store SDK credentials for internal meeting access:

curl -X POST "https://api.meetingbaas.com/v2/zoom-credentials" \
     -H "Content-Type: application/json" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY" \
     -d '{
           "name": "Production Zoom App",
           "client_id": "YOUR_ZOOM_CLIENT_ID",
           "client_secret": "YOUR_ZOOM_CLIENT_SECRET"
         }'
import requests

response = requests.post(
    "https://api.meetingbaas.com/v2/zoom-credentials",
    headers={
        "Content-Type": "application/json",
        "x-meeting-baas-api-key": "YOUR-API-KEY",
    },
    json={
        "name": "Production Zoom App",
        "client_id": "YOUR_ZOOM_CLIENT_ID",
        "client_secret": "YOUR_ZOOM_CLIENT_SECRET"
    }
)
print(response.json())
const response = await fetch("https://api.meetingbaas.com/v2/zoom-credentials", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-meeting-baas-api-key": "YOUR-API-KEY",
  },
  body: JSON.stringify({
    name: "Production Zoom App",
    client_id: "YOUR_ZOOM_CLIENT_ID",
    client_secret: "YOUR_ZOOM_CLIENT_SECRET",
  }),
});
console.log(await response.json());

Response:

{
  "success": true,
  "data": {
    "credential_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "name": "Production Zoom App",
    "credential_type": "app",
    "zoom_user_id": null,
    "zoom_account_id": null,
    "zoom_email": null,
    "zoom_display_name": null,
    "scopes": null,
    "state": "active",
    "last_error_message": null,
    "last_error_at": null,
    "extra": null,
    "created_at": "2026-02-10T10:00:00Z",
    "updated_at": "2026-02-10T10:00:00Z"
  }
}

Save the credential_id — you'll use it when creating bots.

User Credentials (with OAuth)

After a user completes the OAuth consent flow, exchange the authorization code for tokens:

curl -X POST "https://api.meetingbaas.com/v2/zoom-credentials" \
     -H "Content-Type: application/json" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY" \
     -d '{
           "name": "John Doe - Acme Corp",
           "client_id": "YOUR_ZOOM_CLIENT_ID",
           "client_secret": "YOUR_ZOOM_CLIENT_SECRET",
           "authorization_code": "AUTHORIZATION_CODE_FROM_ZOOM",
           "redirect_uri": "https://your-app.com/oauth/callback"
         }'
import requests

response = requests.post(
    "https://api.meetingbaas.com/v2/zoom-credentials",
    headers={
        "Content-Type": "application/json",
        "x-meeting-baas-api-key": "YOUR-API-KEY",
    },
    json={
        "name": "John Doe - Acme Corp",
        "client_id": "YOUR_ZOOM_CLIENT_ID",
        "client_secret": "YOUR_ZOOM_CLIENT_SECRET",
        "authorization_code": "AUTHORIZATION_CODE_FROM_ZOOM",
        "redirect_uri": "https://your-app.com/oauth/callback"
    }
)
print(response.json())
const response = await fetch("https://api.meetingbaas.com/v2/zoom-credentials", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-meeting-baas-api-key": "YOUR-API-KEY",
  },
  body: JSON.stringify({
    name: "John Doe - Acme Corp",
    client_id: "YOUR_ZOOM_CLIENT_ID",
    client_secret: "YOUR_ZOOM_CLIENT_SECRET",
    authorization_code: "AUTHORIZATION_CODE_FROM_ZOOM",
    redirect_uri: "https://your-app.com/oauth/callback",
  }),
});
console.log(await response.json());

Response:

{
  "success": true,
  "data": {
    "credential_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
    "name": "John Doe - Acme Corp",
    "credential_type": "user",
    "zoom_user_id": "SeJwoMGwTCu52501SbDC0Q",
    "zoom_account_id": "AplWZ5oMSouJOw9zu0cmKQ",
    "zoom_email": "john.doe@acme.com",
    "zoom_display_name": "John Doe",
    "scopes": "user:read:token,user:read:user,user:read:zak",
    "state": "active",
    "last_error_message": null,
    "last_error_at": null,
    "extra": null,
    "created_at": "2026-02-10T10:00:00Z",
    "updated_at": "2026-02-10T10:00:00Z"
  }
}

Important: The redirect_uri must exactly match the URI registered in your Zoom app and used in the OAuth authorization URL.

Showing the connected account in your UI: zoom_email and zoom_display_name are captured from Zoom's /users/me API at OAuth time. Use them to show users which Zoom account is connected — particularly helpful when a user has multiple Zoom accounts and needs to verify the right one is linked.

Attaching Custom Metadata with extra

Both POST /v2/zoom-credentials and PATCH /v2/zoom-credentials/{id} accept an optional extra JSON object that lets you tag the credential with arbitrary key/value pairs your application cares about. The API stores it as-is and never interprets it.

curl -X POST "https://api.meetingbaas.com/v2/zoom-credentials" \
     -H "Content-Type: application/json" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY" \
     -d '{
           "name": "John Doe - Acme Corp",
           "client_id": "YOUR_ZOOM_CLIENT_ID",
           "client_secret": "YOUR_ZOOM_CLIENT_SECRET",
           "authorization_code": "AUTHORIZATION_CODE_FROM_ZOOM",
           "redirect_uri": "https://your-app.com/oauth/callback",
           "extra": {
             "internal_user_id": "u_42",
             "environment": "production",
             "tenant": "acme"
           }
         }'

Common uses:

  • Correlate the credential with a record in your own database (e.g. internal_user_id)
  • Tag credentials with environment, region, or tenant for later filtering
  • Store anything else your app needs without maintaining a side table

To clear the metadata on an existing credential, send "extra": null in a PATCH request.

Active Apps Notifier (AAN) Attribution

When a bot joins a Zoom meeting, Zoom displays the app name in the Active Apps Notifier (AAN) — a notice visible to all participants showing which apps are accessing meeting content. The app name is determined by the SDK credentials used to initialize the session.

Zoom Marketplace Requirement: During Marketplace review, Zoom requires the AAN to display your app name. If the AAN shows "Meeting Baas" instead of your product name, the reviewer may flag this. See Zoom's AAN documentation.

How Credentials Control the AAN

When you store a credential (either app or user type) and reference it via credential_id in zoom_config, the bot uses your app's SDK credentials for the session. This means the AAN displays your app name instead of "Meeting Baas".

  • With a stored credential: AAN shows your app name
  • Without a credential (e.g., only obf_token or obf_token_url): AAN shows "Meeting Baas"

Combining App-Only Credentials with External OBF

If you manage OBF tokens yourself (via obf_token or obf_token_url) but still want the AAN to show your app name, store an app-only credential and combine it with your OBF method:

{
  "bot_name": "Recording Bot",
  "meeting_url": "https://zoom.us/j/123456789",
  "zoom_config": {
    "credential_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "obf_token_url": "https://your-api.com/zoom/obf-token"
  }
}

This gives you the best of both worlds:

  • Your SDK credentials from the stored credential control the AAN (your app name)
  • Your OBF backend continues handling token generation (no changes needed)

You can also manage credentials through the Meeting BaaS Dashboard under the Zoom Credentials section, so you don't need to use the API directly.

To find your SDK credentials in the Zoom Marketplace, see: Get Meeting SDK Credentials

v1 API users: In v1, you must pass zoom_sdk_id and zoom_sdk_pwd with every bot request to control the AAN. v2's credential storage is more secure — store once, reference by ID. See the Migration Guide for details.

Using Credentials with Bots

Reference the stored credential directly:

{
  "bot_name": "Recording Bot",
  "meeting_url": "https://zoom.us/j/123456789",
  "zoom_config": {
    "credential_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901"
  }
}

By Zoom User ID

Look up a credential by the Zoom user ID:

{
  "bot_name": "Recording Bot",
  "meeting_url": "https://zoom.us/j/123456789",
  "zoom_config": {
    "credential_user_id": "SeJwoMGwTCu52501SbDC0Q"
  }
}

This is useful when you store the Zoom user ID in your database and want to find the matching credential automatically.

Listing Credentials

Get all credentials for your team:

curl "https://api.meetingbaas.com/v2/zoom-credentials" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY"
import requests

response = requests.get(
    "https://api.meetingbaas.com/v2/zoom-credentials",
    headers={"x-meeting-baas-api-key": "YOUR-API-KEY"}
)
for cred in response.json()["data"]:
    print(f"{cred['name']}: {cred['credential_type']} ({cred['state']})")
const response = await fetch("https://api.meetingbaas.com/v2/zoom-credentials", {
  headers: { "x-meeting-baas-api-key": "YOUR-API-KEY" },
});
const { data } = await response.json();
data.forEach(cred => {
  console.log(`${cred.name}: ${cred.credential_type} (${cred.state})`);
});

Response:

{
  "success": true,
  "data": [
    {
      "credential_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "name": "Production Zoom App",
      "credential_type": "app",
      "zoom_user_id": null,
      "zoom_email": null,
      "zoom_display_name": null,
      "state": "active",
      "extra": null,
      ...
    },
    {
      "credential_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
      "name": "John Doe - Acme Corp",
      "credential_type": "user",
      "zoom_user_id": "SeJwoMGwTCu52501SbDC0Q",
      "zoom_email": "john.doe@acme.com",
      "zoom_display_name": "John Doe",
      "state": "active",
      "extra": { "internal_user_id": "u_42", "environment": "production" },
      ...
    }
  ]
}

Filtering

The list endpoint accepts the following optional query parameters. All filters combine with AND.

ParameterBehaviour
nameCase-insensitive partial match on name
zoom_emailCase-insensitive partial match on zoom_email
zoom_display_nameCase-insensitive partial match on zoom_display_name
zoom_user_idExact match on zoom_user_id
credential_typeComma-separated list (app, user)
stateComma-separated list (active, invalid)
extraMatch values in the extra JSON payload using key:value syntax. Multiple conditions are comma-separated and must all match. Values are matched exactly (case-sensitive); credentials missing the key are excluded.

Examples:

# Find a specific user's credentials by email substring
curl "https://api.meetingbaas.com/v2/zoom-credentials?zoom_email=john.doe" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY"

# Only invalid user-type credentials
curl "https://api.meetingbaas.com/v2/zoom-credentials?credential_type=user&state=invalid" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY"

# Filter by your own metadata stored in `extra`
curl "https://api.meetingbaas.com/v2/zoom-credentials?extra=internal_user_id:u_42,environment:production" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY"

Getting a Single Credential

Retrieve details for a specific credential:

curl "https://api.meetingbaas.com/v2/zoom-credentials/b2c3d4e5-f6a7-8901-bcde-f12345678901" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY"

Updating Credentials

Update an existing credential's name, SDK credentials, or re-authorize with new OAuth tokens.

Update Name

curl -X PATCH "https://api.meetingbaas.com/v2/zoom-credentials/b2c3d4e5-f6a7-8901-bcde-f12345678901" \
     -H "Content-Type: application/json" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY" \
     -d '{
           "name": "Jane Doe - Acme Corp"
         }'

Update SDK Credentials

Update both client ID and secret together:

curl -X PATCH "https://api.meetingbaas.com/v2/zoom-credentials/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
     -H "Content-Type: application/json" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY" \
     -d '{
           "client_id": "NEW_ZOOM_CLIENT_ID",
           "client_secret": "NEW_ZOOM_CLIENT_SECRET"
         }'

Re-authorize with New OAuth Tokens

If a credential becomes invalid (user revoked access, tokens expired), you can re-authorize by providing a new authorization code. This resets the credential state to "active" and clears any error messages:

curl -X PATCH "https://api.meetingbaas.com/v2/zoom-credentials/b2c3d4e5-f6a7-8901-bcde-f12345678901" \
     -H "Content-Type: application/json" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY" \
     -d '{
           "client_id": "YOUR_ZOOM_CLIENT_ID",
           "client_secret": "YOUR_ZOOM_CLIENT_SECRET",
           "authorization_code": "NEW_AUTHORIZATION_CODE",
           "redirect_uri": "https://your-app.com/oauth/callback"
         }'

Re-authorizing is useful when a credential becomes invalid. Instead of deleting and recreating, update the existing credential to preserve the same credential_id in your system.

Deleting Credentials

Remove a credential and its stored tokens:

curl -X DELETE "https://api.meetingbaas.com/v2/zoom-credentials/b2c3d4e5-f6a7-8901-bcde-f12345678901" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY"

Deleting a credential removes all stored tokens. Bots using this credential will fail to join meetings. The Zoom user would need to re-authorize your app to create a new credential.

Credential States

Active

The credential is working and can be used to join meetings.

Invalid

The credential has failed. Common reasons:

  • User revoked app access in Zoom settings
  • OAuth token refresh failed
  • Zoom account was deactivated
  • Required scopes were removed from the app

When a credential becomes invalid:

  1. Check last_error_message for details
  2. Prompt the user to re-authorize your app
  3. Update the existing credential with the new authorization code using PATCH /v2/zoom-credentials/{id} (this preserves the credential ID and resets the state to "active")

Example invalid credential:

{
  "credential_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
  "name": "John Doe - Acme Corp",
  "credential_type": "user",
  "state": "invalid",
  "last_error_message": "Token refresh failed: invalid_grant",
  "last_error_at": "2026-02-09T15:30:00Z",
  ...
}

Security

Encryption

All sensitive data is encrypted at rest:

  • Client secrets: AES-256-GCM encrypted
  • Access tokens: AES-256-GCM encrypted
  • Refresh tokens: AES-256-GCM encrypted

What's Never Returned

API responses never include:

  • Client secrets
  • Access tokens
  • Refresh tokens
  • Encryption keys

You only receive metadata (IDs, names, states, timestamps).

Access Control

Credentials are scoped to your team (API key). One team cannot access another team's credentials.

Error Handling

Creation Errors

StatusMeaning
400 Bad RequestMissing required fields or invalid input
400 Bad Requestredirect_uri missing when authorization_code provided
400 Bad RequestAuthorization code exchange failed (invalid code or URI mismatch)

Common OAuth Exchange Failures

"invalid_grant": The authorization code has expired (valid for ~10 minutes) or was already used. Start a new OAuth flow.

"redirect_uri_mismatch": The redirect_uri doesn't match what was used in the authorization URL. Ensure exact match including trailing slashes.

"invalid_client": The client ID or secret is incorrect. Verify your Zoom app credentials.

Best Practices

Naming Conventions

Use descriptive names that help you identify credentials:

  • App credentials: Include environment (e.g., "Production Zoom App", "Staging Bot")
  • User credentials: Include user identifier (e.g., "John Doe - Acme Corp", "user@company.com")

Monitoring Credential Health

Periodically check for invalid credentials:

import requests

response = requests.get(
    "https://api.meetingbaas.com/v2/zoom-credentials",
    headers={"x-meeting-baas-api-key": "YOUR-API-KEY"}
)

for cred in response.json()["data"]:
    if cred["state"] == "invalid":
        print(f"Invalid credential: {cred['name']}")
        print(f"  Error: {cred['last_error_message']}")
        print(f"  Since: {cred['last_error_at']}")
        # Notify user to re-authorize

Handle Revocations

Users can revoke your app's access in their Zoom settings. When this happens:

  1. The credential state becomes invalid
  2. Bots using this credential will fail
  3. Prompt the user to re-authorize
  4. Create a new credential and delete the old one

FAQ

Q: How many credentials can I store?

There's no hard limit. Store as many as you need for your users.

Q: What can I update on an existing credential?

You can update the name, SDK credentials (client ID and secret together), or re-authorize with new OAuth tokens using PATCH /v2/zoom-credentials/{id}. Re-authorizing is useful when a credential becomes invalid.

Q: What happens to bots when a credential becomes invalid?

Bots created with that credential will fail to join with a ZOOM_ACCESS_TOKEN_ERROR or similar error. Already-running bots are not affected.

Q: How long are OAuth tokens valid?

Zoom access tokens expire after 1 hour. Meeting BaaS automatically refreshes them using the refresh token. If refresh fails, the credential becomes invalid.

Q: Do I need separate credentials for SDK and OBF?

For internal meetings: App-only credentials are sufficient. For external meetings: You need user credentials (with OAuth) for OBF token support.

Next Steps

On this page