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/meAPI at OAuth time, requires theuser:read:userscope) - Granted scopes
- Optional
extraJSON 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_tokenorobf_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
By Credential ID (Recommended)
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.
| Parameter | Behaviour |
|---|---|
name | Case-insensitive partial match on name |
zoom_email | Case-insensitive partial match on zoom_email |
zoom_display_name | Case-insensitive partial match on zoom_display_name |
zoom_user_id | Exact match on zoom_user_id |
credential_type | Comma-separated list (app, user) |
state | Comma-separated list (active, invalid) |
extra | Match 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:
- Check
last_error_messagefor details - Prompt the user to re-authorize your app
- 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
| Status | Meaning |
|---|---|
400 Bad Request | Missing required fields or invalid input |
400 Bad Request | redirect_uri missing when authorization_code provided |
400 Bad Request | Authorization 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-authorizeHandle Revocations
Users can revoke your app's access in their Zoom settings. When this happens:
- The credential state becomes
invalid - Bots using this credential will fail
- Prompt the user to re-authorize
- 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
- OBF Token Support — Use credentials for external meetings
- OAuth Consent Flow — Build the user authorization flow
- Zoom App Setup — Create or configure your Zoom app
