Meeting Recording API Fails

I am getting an error message of “This API does not support client credentials for authorization.” when trying to dump the CLosed Captioning transcript of a Zoom meeting. I looked on the Zoom Website and confirmed that the Closed Caption transcript exists. Any help would be greatly appreciated:

Python Script Below

import requests
import json

# Set up variables for Zoom OAuth endpoint and app credentials
oauth_endpoint = "https://zoom.us/oauth/token"
client_id = "YOUR_CLIENT_ID_HERE"
client_secret = "YOUR_CLIENT_SECRET_HERE"

# Set up variables for meeting ID and recording file index
meeting_id = "MEETING ID"
recording_file_index = 0  # Index of the recording file to download

# Request an OAuth token from the Zoom API
response = requests.post(oauth_endpoint, data={
    "grant_type": "client_credentials",
    "client_id": client_id,
    "client_secret": client_secret
})

# Check if the request was successful and get the access token
if response.status_code == 200:
    access_token = response.json()["access_token"]
    print(f"Access token: {access_token}")
else:
    print(f"Error requesting access token: {response.text}")
    exit()

# Set up headers with authorization and content type for API request
headers = {
    "Authorization": f"Bearer {access_token}",
    "Content-Type": "application/json"
}

# Make GET request to Zoom API to get recording files for meeting ID
url = f"https://api.zoom.us/v2/meetings/{meeting_id}/recordings"
response = requests.get(url, headers=headers)

# Check if the request was successful and get the transcript download URL
if response.status_code == 200:
    try:
        transcript_url = response.json()["recording_files"][recording_file_index]["download_url"]
    except (KeyError, IndexError):
        print("Error parsing recording files JSON")
        exit()
else:
    print(f"Error retrieving recording files: {response.text}")
    exit()

# Make GET request to download the transcript
response = requests.get(transcript_url)

# Check if the request was successful and print the transcript
if response.status_code == 200:
    transcript = response.text
    print(transcript)
else:
    print(f"Error downloading transcript: {response.text}")
    exit()

Before Creating a New Topic:

If you’re experiencing unexpected API/Zoom API Events (webhooks) behavior please search the forum with relevant keywords (e.x. error message) and follow the guidance outlined in those posts. Please also leverage the following support links:


Format Your New Topic as Follows:

API Endpoint(s) and/or Zoom API Event(s)
Link the API endpoint(s) and/orZoom API Event(s) you’re working with to help give context.

Description
Details on your question, workflow or the problem you’re trying to solve.

Error?
The full error message or issue you are running into, where applicable.

How To Reproduce
Steps to reproduce the behavior:
1. Request URL / Headers (without credentials or sensitive info like emails, uuid, etc.) / Body
2. Authentication method or app type
3. Any errors

Hi @blase.sacus
Thanks for reaching out to the Zoom Developer Forum.
Could you please confirm what app type are you using?
If you have set up a Server-to-Server Oauth app, the error might be on the way that you are requesting your access token.

Instead of using “client_credentials” in grant_type, you will have to switch it to “account_credentials”.
Here is a helpful post on How to User Server to Server OAuth app with Postman that might help you to troubleshoot where the error could be:

Let me know if this helps,
Elisa

Thanks @elisa.zoom, I tried switching it to client_credentials but I am now getting the

Error requesting access token: {“reason”:“unsupported grant type”,“error”:“unsupported_grant_type”}

So it doesn’t seem to be an issue with the client credentials. I am using a Server to Server Oauth app. Appreciate any help and guidance you could provide.

Hi @blase.sacus , she mentioned to not use client_credentials if it’s server-to-server OAuth :slight_smile: – is that what you’re using? If so, please reference the guide in full.

If you’re using our standard OAuth, grant_type is authorization_code. Headers content-type should be application/x-www-form-urlencoded. See other necessary inputs:

Here’s the breakdown in Postman .

1 Like

@gianni.zoom or @elisa.zoom

Sorry, I typed too quickly. I meant that when I switched to account_credentials I had gotten the following error:

Error requesting access token: {“reason”:“unsupported grant type”,“error”:“unsupported_grant_type”}

I read through the documentation and was still unclear. When I am calling the OAuth, I use the following:

# Request an OAuth token from the Zoom API
response = requests.post(oauth_endpoint, data={
    "grant_type": "account_credentials",
    "client_id": client_id,
    "client_secret": client_secret
})

But based on the script, it is failing. I would appreciate if you could take a look at the script and let me know where I am falling down. I have been trying to get this to work for a few weeks.

import requests
import json

# Set up variables for Zoom OAuth endpoint and app credentials
oauth_endpoint = "https://zoom.us/oauth/token"
client_id = "CLIENT ID"
client_secret = "SECRET"

# Set up variables for meeting ID and recording file index
meeting_id = "MEETING ID"
recording_file_index = 0  # Index of the recording file to download

# Request an OAuth token from the Zoom API
response = requests.post(oauth_endpoint, data={
    "grant_type": "account_credentials",
    "client_id": client_id,
    "client_secret": client_secret
})

# Check if the request was successful and get the access token
if response.status_code == 200:
    access_token = response.json()["access_token"]
    #print(f"Access token: {access_token}")
else:
    print(f"Error requesting access token: {response.text}")
    exit()

# Set up headers with authorization and content type for API request
headers = {
    "Authorization": f"Bearer {access_token}",
    "Content-Type": "application/json"
}

# Make GET request to Zoom API to get recording files for meeting ID
url = f"https://api.zoom.us/v2/meetings/{meeting_id}/recordings/transcript"
response = requests.get(url, headers=headers)

# Check if the request was successful and get the transcript download URL
if response.status_code == 200:
    try:
        transcript_url = response.json()["recording_files"][recording_file_index]["download_url"]
    except (KeyError, IndexError):
        print("Error parsing recording files JSON")
        exit()
else:
    print(f"Error retrieving recording files: {response.text}")
    exit()

# Make GET request to download the transcript
response = requests.get(transcript_url)

# Check if the request was successful and print the transcript
if response.status_code == 200:
    transcript = response.text
    print(transcript)
else:
    print(f"Error downloading transcript: {response.text}")
    exit()

Hi @blase.sacus
The grant_type “account_credentials” is correct only IF you are using a Server-to-Server OAuth app.

Also, there is another error in your request. For the server-to-server Oauth app, you also need to pass the account_id and use the client id and client secret as your authorization method.

Here is a link to our docs:
https://marketplace.zoom.us/docs/guides/build/server-to-server-oauth-app/#use-account-credentials-to-get-an-access-token

Here is a link to one of our Sample apps available on Github for token generation:

Thanks @elisa.zoom

I made changes to the script to use account credentials. I am able to get the OAuth access token and the HTTP response code is 200.

Next I take the access token and use it to populate the authorization header when trying to retrieve the transcript but I am getting the following error:

“code”:124,“message”:“This API does not support client credentials for authorization.”

Shouldn’t I use the access token when calling the meetings endpoint?

Code Block where I think there is an issue:

# Set up the API endpoint URLs and headers
meeting_id = "123456789" # Replace with the actual meeting ID

api_url = f"https://api.zoom.us/v2/meetings/{meeting_id}/recordings"

auth_header = {"Authorization": f"Bearer {response_data['access_token']}" } # Replace with the actual OAuth access token

Complete Python script:

import requests
import base64
import json

# Replace these values with your own
ZOOM_ACCOUNT_ID = 'ACCOUNTID'
ZOOM_CLIENT_ID = 'CLIENTID'
ZOOM_CLIENT_SECRET = 'SECRET'


ZOOM_OAUTH_ENDPOINT = 'https://zoom.us/oauth/token'

# Create the authorization header for the API request
auth_header = base64.b64encode(f'{ZOOM_CLIENT_ID}:{ZOOM_CLIENT_SECRET}'.encode('ascii')).decode('ascii')
headers = {
    'Authorization': f'Basic {auth_header}',
    'Content-Type': 'application/x-www-form-urlencoded',
}

# Send the request to the Zoom API to obtain an OAuth token
data = {
    'grant_type': 'client_credentials',
    'account_id': ZOOM_ACCOUNT_ID,
}
response = requests.post(ZOOM_OAUTH_ENDPOINT, data=data, headers=headers)
response_data = response.json()
#print (response_data)
#print ('OAuth response <-')
# Print the access token for the API response
print(f"Access token: {response_data['access_token']}")

# Set up the API endpoint URLs and headers
meeting_id = "123456789"  # Replace with the actual meeting ID
api_url = f"https://api.zoom.us/v2/meetings/{meeting_id}/recordings"
auth_header = {"Authorization": f"Bearer {response_data['access_token']}" }  # Replace with the actual OAuth access token
print (auth_header)
# Make a GET request to retrieve the list of recordings for the meeting
response = requests.get(api_url, headers=auth_header)
response_data = json.loads(response.text)
print (response.text)
# Parse the response to find the recording ID of the meeting's transcript
transcript_recording_id = ""
for recording in response_data["recording_files"]:
    if recording["recording_type"] == "audio_transcript":
        transcript_recording_id = recording["id"]
        break

# Construct the API endpoint URL for downloading the transcript
if transcript_recording_id:
    transcript_url = f"https://api.zoom.us/v2/meetings/{meeting_id}/recordings/{transcript_recording_id}/transcripts"
    transcript_response = requests.get(transcript_url, headers=auth_header)
    transcript_data = json.loads(transcript_response.text)
    # Do something with the transcript data
else:
    print("No transcript recording found.")



print (auth_header)

@blase.sacus ,

It looks like you may be mixing up authorization methods between Server-to-Server OAuth and regular OAuth app types which could be affecting the token. I am not able to reproduce your results. With a valid Sever-to-Server OAuth or standard OAuth access token, I can successfully call that endpoint.

Double check if you are using a Server-to-Server OAuth app in the marketplace? If yes, get your token this way: Postman

The key value pairs are passed as query parameters.

For standard OAuth app, place the key value pairs in the request body:
https://www.postman.com/zoom-developer/workspace/zoom-public-workspace/request/22097587-82698a23-7af5-4f3f-bf2e-a62c3eb8903d

@gianni.zoom

Sorry, I am not clear. When I call the OAUTH Endpoint, I am getting an Access Token and an HTTP status code of 200 so doesn’t that mean it is being authorized correctly?

I am having an issue when calling the recording endpoint with the access token from the authorization request.

Also, could you let me know how to confirm that the app is set-up as Server to Server. It wasn’t intuitive when looking in the Marketplace GUI.

@blase.sacus ,

Log in to marketplace.zoom.us.

Click “manage” . It’s the button to the left of your user icon in the top right corner.

You will see a list of “Created Apps” . Look for the column called “Type” . It will say what type of apps you’re using.

Additionally, if you click into the app for which you are using these credentials, underneath the app title it should say what type of app it is.

Please confirm the app type and then I can continue to best help you :slight_smile:

@gianni.zoom
It shows the type as OAuth and User managed.

@blase.sacus , thanks for clarifying. This means you incorrectly created your access token for that app type which is likely why it was not working.

Please see this example for standard OAuth which does not take account_credentials: https://www.postman.com/zoom-developer/workspace/zoom-public-workspace/request/22097587-82698a23-7af5-4f3f-bf2e-a62c3eb8903d

Thank you!

@gianni.zoom
Would I be able to switch it to server to server?

You would have to create a new app and select server-to-server @blase.sacus

@gianni.zoom

Okay, I have most of the script working. I am now able to Authorize, and get the Access token. The question I have now is that when I try and download the transcript, it is a web page and not a text file. Shouldn’t transcripts be text? Does the meeting password need to be passed?

import webbrowser
import http.server
import socketserver
import urllib.parse
import requests
import json

# Set up the OAuth app credentials
client_id = "CLIENT_ID"
client_secret = "CLIENT_SECRET"
redirect_uri = "http://localhost:8080/"

# Set up the authorization API endpoint
auth_url = "https://zoom.us/oauth/authorize"

# Set up the authorization parameters
params = {
    "response_type": "code",
    "client_id": client_id,
    "redirect_uri": redirect_uri,
    "state": "RANDOM_STRING"
}

# Build the authorization URL
auth_endpoint = auth_url + "?" + urllib.parse.urlencode(params)

# Open the authorization URL in the user's web browser
webbrowser.open(auth_endpoint)

# Set up the web server to listen for the authorization code
class AuthorizationHandler(http.server.SimpleHTTPRequestHandler):
    def do_GET(self):
        # Get the authorization code from the query string
        code = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query).get("code")
        if code:
            # Print the authorization code
            print(f"Authorization code: {code[0]}")

            # Set up the OAuth app credentials and API endpoint
            print("Setting up the OAuth app credentials and API endpoint")
            auth_url = "https://zoom.us/oauth/token"

            # Set up the authorization parameters
            data = {
                "grant_type": "authorization_code",
                "code": code[0],
                "redirect_uri": redirect_uri,
                "client_id": client_id,
                "client_secret": client_secret
            }

            # Send a POST request to the API endpoint to get the access token
            response = requests.post(auth_url, data=data)

            # Check if the request was successful
            if response.status_code == 200:
                
                # Print the access token
                access_token = response.json()["access_token"]
                print(f"Access token: {access_token}")

                # Set the Meeting ID
                MEETING_ID = '11111111111'

                # Set the API endpoint for the transcript download
                api_endpoint = f'https://api.zoom.us/v2/meetings/{MEETING_ID}/recordings'

                # Set the request headers
                headers = {
                    'Authorization': f'Bearer {access_token}',
                    'Content-Type': 'application/json'
                }

                # Make the API request to get the recordings
                response = requests.get(api_endpoint, headers=headers)

                # Check if the request was successful
                if response.status_code == 200:
                    # Print the response JSON for debugging
                    #print("Response JSON:")
                    #print(json.dumps(response.json(), indent=4))

                    # Parse the response to get the download URL for the transcript
                    #meetings = response.json().get('meetings', [])
                    #print("Response JSON2:")
                    #print(len(meetings))

                    #meetings1 = response.json().get('recording_files', [])
                    #print("Response JSON2:")
                    #print(len(meetings1))

                    #if len(meetings) > 0:

                    # Parse the response to get the download URL for the transcript
                    recordings = response.json()['recording_files']
                    transcript_url = next((recording['download_url'] for recording in recordings if recording['file_type'] == 'TRANSCRIPT'), None)
                    print("Transcript URL")
                    print(transcript_url)

                # Download the transcript file
                if transcript_url:
                    response = requests.get(transcript_url)
                    with open('transcript.txt', 'wb') as f:
                        f.write(response.content)
                    print('Transcript downloaded successfully.')
                else:
                    print('No transcript file found.')
            else:
                # Print the error message
                error_message = response.json().get("error_description")
                if error_message:
                    print(f"Error: {error_message}")
                else:
                    print("Unknown error occurred.")
        else:
            print("Authorization code not found")
        # Stop the web server
        self.server.shutdown()

with socketserver.TCPServer(("", 8080), AuthorizationHandler) as httpd:
    # Start the web server
    print("Waiting for authorization code...")
    httpd.serve_forever()

Hi @blase.sacus ,

I’m happy the access token is working!

TRANSCRIPT files are in VTT format, CHAT is as TXT files. Try transcript.vtt and then convert to TXT if that is preferred.

This topic was automatically closed 368 days after the last reply. New replies are no longer allowed.