The final result of this post is a functional Python Flask web server application that queries YouTube information using the YouTube Data API v3 (YouTube API) and OAuth 2.0 authorisation. We review the official documentation and discuss how to set up a Google Cloud Project that wraps the YouTube API. Next, we describe how to obtain credentials, including both the API key and OAuth 2.0 tokens, for the project. Finally, we present a functional Python Flask web server application. This example application also includes a function to manage the refresh_token, which is available only once during the first OAuth 2.0 authorization request.

108-feature-image.png
Using the YouTube Data API v3 with API Keys and OAuth 2.0

A few years ago, I experimented with the YouTube API using NodeJs. Unfortunately, I didn’t document that learning process, nor did I complete the project. Now, I’m revisiting this topic, but this time, I’m using Python and documenting my progress in this post.

My goal is straightforward: for my own YouTube channel, I want to retrieve specific information about each video, including the ID, title, description, search tags, and file name.

Interestingly, while most of this information can be obtained using an API key, retrieving the video file name requires OAuth 2.0 authorisation. As a result, we’ll need both an API key and OAuth 2.0 credentials.

💥 Please note, my Gmail address for my developer account and YouTube channel is the same.

The primary objective of this post is to document the key points for getting started with the YouTube API, rather than creating a complete working application. In terms of coding, we will modify an existing official YouTube Data API v3 example.

Table of contents

❶ Google APIs come with an extensive set of documentation and examples, which I find more than sufficient. However, I did get a bit lost in it. In this post, I’m listing the resources I’ve found most relevant for my needs. I encourage you to explore them as well. Keep in mind that this list is not exhaustive. While you can jump directly to the create a new project section, reading through these resources will make the later sections much easier to follow. Skipping the reading might not be the best approach.

⓵ Note on OAuth 2.0 Authorized Redirect URIs:

In the section Create authorization credentials, specifically point 4, please pay attention to the following statement:

For testing, you can specify URIs that refer to the local machine, such as http://localhost:8080. With that in mind, please note that all of the examples in this document use http://localhost:8080 as the redirect URI.

We recommend that you design your app’s auth endpoints so that your application does not expose authorization codes to other resources on the page.

Similarly, in the section Step 1: Set authorization parameters, the explanation for the required parameter redirect_uri reads:

Determines where the API server redirects the user after the user completes the authorization flow. The value must exactly match one of the authorized redirect URIs for the OAuth 2.0 client, which you configured in your client’s API Console Credentials page. If this value doesn’t match an authorized redirect URI for the provided client_id you will get a redirect_uri_mismatch error.

Note that the http or https scheme, case, and trailing slash (‘/’) must all match.

Please note that, in a later section, we configure the value of redirect_uri to http://localhost:5000/oauth2callback.

⓶ Note on OAuth 2.0 Refresh Token:

While reading, I did take note of essential points about the refresh token. However, it didn’t fully register at the time. Initially, I got the code to work. But when I returned to run it again 2 or 3 days later, I encountered a RefreshError exception. I’ve included this section after troubleshooting the exception.

💥 Despite the extensive amount of documentation, it is not explicitly stated that the refresh_token is provided only once, during the initial authorisation from users. This Stack Overflow answer clarifies this point.

● Note the example immediately preceding the section Refreshing an access token (offline access):

authorization_url, state = flow.authorization_url(
    # Enable offline access so that you can refresh an access token without
    # re-prompting the user for permission. Recommended for web server apps.
    access_type='offline',
    # Enable incremental authorization. Recommended as a best practice.
    include_granted_scopes='true')

In my understanding, this section reiterates and emphasizes that it is more beneficial to set access_type to offline. The reason is included in the comment in the above code block.

● This document, Implementing OAuth 2.0 Authorization, also reinforces the same point using different wording:

For example, a server-side web application exchanges the returned token for an access token and a refresh token. The access token lets the application authorize requests on the user’s behalf, and the refresh token lets the application retrieve a new access token when the original access token expires.

● And the last one I would like to quote is from the Python official example documentation:

Refresh and access tokens: When a user grants your application access, the OAuth 2.0 authorization server provides your application with refresh and access tokens. These tokens are only valid for the scope requested. Your application uses access tokens to authorize API calls. Access tokens expire, but refresh tokens do not. Your application can use a refresh token to acquire a new access token.

Warning: Keep refresh and access tokens private. If someone obtains your tokens, they could use them to access private user data.

🚀 To sum up, developers must save the refresh token in permanent storage and include it during subsequent authorisation requests. This ensures that a new access token can be generated if the current access token expires.

⓷ Note on OAuth 2.0 RefreshError Exception:

As mentioned in the previous section, I encountered the RefreshError exception because the access token had expired, and I had not included the refresh_token in the authorisation request. The text of the exception is:

google.auth.exceptions.RefreshError: The credentials do not contain the necessary fields need
to refresh the access token. You must specify refresh_token, token_uri, client_id, and client_secret.

Fortunately, this problem has been thoroughly discussed in the following GitHub issue: https://github.com/googleapis/google-auth-library-python/issues/659.

🚀 Summary of the Solution:

  1. Visit https://myaccount.google.com/u/0/permissions.
  2. Select youtube data api scraper.
  3. Click on Delete all connections you have with youtube data api scraper.
  4. This action revokes the previously granted authorisation.
  5. The next time we request authorisation, the Google Cloud API will guide us through the entire OAuth 2.0 user authorisation process again.
  6. Ensure that we save the refresh_token to permanent storage this time.

❷ Create a Google Cloud Project using the Google Cloud Console. This project will house the YouTube Data API. Please follow the steps below.

  1. Sign into your Google account. Typically, signing into Gmail is sufficient.
  2. Navigate to the Google Cloud Console: https://console.cloud.google.com. If this is your first time accessing the Cloud Console, you’ll encounter a welcome pop-up window. Agree to the Terms of Service, then click the AGREE AND CONTINUE button.
  3. On the Google Cloud page:
    • Click on the project drop-down list in the top right-hand corner (RHS).
    • In the pop-up window, click the NEW PROJECT button on the RHS.

Please refer to the screenshot illustration below:

108-01.png

  1. On the New Project page, enter an appropriate project name, such as YouTube 02.
  2. Click on the CREATE button.
  3. After the project is created, select it. You will be taken to a screen as shown below:

108-02.png

  1. Click on the API Explore and enable APIs link.
  2. On the new screen, click on the + ENABLE APIS AND SERVICES button (or link) in the top band, toward the left-hand side (LHS).
  3. In the next screen, titled Welcome to the API Library, type “YouTube Data API” into the search box and hit the Enter key.
  4. You will be taken to the result page, which looks similar to the screenshot below:

108-03.png

  1. Select the first entry: YouTube Data API v3.
  2. On the next page, click on the ENABLE button.
  3. This will take you to a new page that looks like the screenshot below:

108-04.png

I am not familiar with the CREATE CREDENTIALS button in the above screen. The next step is to create credentials, both the API key and OAuth 2.0. Click on the Credentials link on the left-hand side (LHS). We will be taken to the following new screen:

108-05.png

⓵ Click on API key to create a new API key.

⓶ Click on OAuth consent screen to start creating a new OAuth 2.0 credential:

  1. For User Type, we can only select External. Then click on CREATE.

  2. On the next screen:

    • Enter something meaningful for App name*. I use youtube data api scraper.

    • For User support email *, I use the email I currently sign in with, which is also the email associated with my YouTube channel.

    • Under Developer contact information , in the Email addresses * section, I also use the same email as the one in User support email *.

    • Click the SAVE AND CONTINUE button.

  1. On the next screen, click on the ADD OR REMOVE SCOPES button. Then select the ../auth/youtube.readonly scope, as illustrated in the screenshot below:

    108-06.png

    Click the UPDATE button. The newly selected scope is now added under the Your sensitive scopes section of the screen.

    Click the SAVE AND CONTINUE button.

  2. On the new screen, under the Test users section, click on + ADD USERS. Use the same email for User support email *.

    Click the SAVE AND CONTINUE button.

  3. You will be taken to the OAuth consent screen summary page. We have completed the OAuth consent.

⓷ Create the OAuth 2.0 Client ID and Secret:

  1. Click on the Credentials link on the LHS.
  2. Then click + CREATE CREDENTIALS and select OAuth client ID, as shown in the screenshot below:

108-07.png

On the new screen:

  1. For Application type *, select Web application.

  2. For Name *, enter something meaningful. I use YouTube 02 Test Client.

  3. Under the section Authorized redirect URIs, click on the + ADD URI button. Specify http://localhost:5000/oauth2callback. Please refer to the quoted documentation for an explanation of why http://localhost:5000 is a valid choice. Later, in the code, we will see how http://localhost:5000/oauth2callback is implemented.

  4. Click the CREATE button. After a few seconds, a OAuth client created notification screen will appear, as illustrated in the screenshot below:

    108-08.png

    Click on DOWNLOAD JSON to save the OAuth 2.0 credential file to disk. Our applications will need this client secrets file.

We have completed creating an OAuth 2.0 credential. We can now use the downloaded credential file in our applications to obtain authorisation for using the YouTube Data API.

🚀 Note: If you already have a project selected, you can directly access its credentials page using the following URL: https://console.cloud.google.com/apis/credentials. Here, you can make changes to existing credentials.

❸ The Billing Account:

Please note that you might be asked to set up a billing account. A few years ago, I set one up, but unfortunately, I didn’t document the process. I don’t recall the exact steps off the top of my head. However, I haven’t been charged anything yet, as my usage has always remained below the free quota.

❹ Google API Playground is a valuable tool that allows us to explore how any API works in terms of parameter requirements. This understanding will greatly assist us when coding later on. For our purposes, we will focus on three specific APIs. The way the playground operates should be consistent across other APIs as well.

In our code example, we will create three methods corresponding to these three APIs. These methods will use OAuth 2.0 authorisation to retrieve data, although an API key can also be used for some methods.

To begin, visit the YouTube Data API documentation. Click on the Search for content header, which will take you to the Search: list page https://developers.google.com/youtube/v3/docs/search/list. On this page, pay attention to the three links: video, channel, and playlist — these are the APIs we are interested in. Also, take note of the  Quota impact warning.

⓵ Retrieving a Channel’s Upload Playlist ID Using a Channel ID:

Click the channel link. On the new page, under the Methods header, click the list link. This will take you to the YouTube Data API Channels: https://developers.google.com/youtube/v3/docs/channels/list.

Please note the following general format:

  • The middle pane contains the documentation for this API, including explanations for all input parameters and the HTTP request endpoint address.
  • The RHS pane is where we can try out the API.

Let’s try with the following parameter values:

  • part: contentDetails
  • id: my own channel ID
  • Leave both Google OAuth 2.0 and API key checked.

Click the Execute button, then follow the instructions. You should receive a response similar to the one below:

{
	"kind": "youtube#channelListResponse",
	"etag": "76zI1v9YitYCtGvsZ6X_K11enRQ",
	"pageInfo": {
		"totalResults": 1,
		"resultsPerPage": 5
	},
	"items": [
		{
			"kind": "youtube#channel",
			"etag": "lXsff8mgRvFZ4QLKCa2LX-6JYDc",
			"id": "UCPB9BdRkJRd3aGm9qAY3CTw",
			"contentDetails": {
				"relatedPlaylists": {
					"likes": "LL",
					"uploads": "UUPB9BdRkJRd3aGm9qAY3CTw"
				}
			}
		}
	]
}

The equivalent HTTP request, using the API key set up when creating your Google Cloud Project, is as follows:

https://www.googleapis.com/youtube/v3/channels?
    key=<API key>&
    part=contentDetails&
    id=<a YouTube channel ID>

⓶ Retrieving Videos from a Channel’s Playlist using the Playlist ID:

On the Search: list page, click the playlist link. Then, on the new page, click the playlistItems.list link:

We can try this API with the following parameter values:

  • part: snippet
  • playlistId: for example, the default upload playlist ID
  • maxResults: 1 to 50
  • Either Google OAuth 2.0 or API key will suffice.

Click the Execute button, then follow the instructions. We should get a valid response. It is rather long, so we will not list it here.

And this is the HTTP request equivalent, using the API key which we have set up when creating our Google Cloud Project:

https://www.googleapis.com/youtube/v3/playlistItems?
   key=<API key>&
   part=snippet&
   playlistId=<a YouTube playlist ID>
   maxResults=2	

⓷ Retrieving a Video’s File Name using a Video ID:

On the Search: list page, click the video link. Then, on the new page, under the Methods header, click the list link:

We can try it with the following parameter values:

  • part: fileDetails
  • id: my own video ID
  • Check only Google OAuth 2.0.

We should get a response similar to the below:

{
	"kind": "youtube#videoListResponse",
	"etag": "hKdZjPcW_VDYbyPRxoJ_WXoYj6A",
	"items": [
		{
			"kind": "youtube#video",
			"etag": "CMbJrlI-T_BK7YezgVi1Jpkb2-0",
			"id": "hMG1JjBwVx4",
			"fileDetails": {
				"fileName": "20240319-con chó giữa chúng ta-1.movie.mp4"
			}
		}
	],
	"pageInfo": {
		"totalResults": 1,
		"resultsPerPage": 1
	}
}

It seems that this API requires OAuth 2.0, which requires an authorisation code. For this reason, I have not tried out the HTTP request. According to the above page, the endpoint is:

GET https://www.googleapis.com/youtube/v3/videos

❺ The Complete Example Application:

We will begin with the quickstart_web.py example from the official YouTube API Samples in Python.

⓵ We will make the following changes:

  1. The scope is set to https://www.googleapis.com/auth/youtube.readonly to match the project scope.
  2. The refresh_token is written into a local JSON file after the first successful authorisation request. In this implementation, we will simply write the entire granted credentials into the local JSON file.
  3. Every time the application needs credentials, it first checks if the credentials JSON file exists. If it does, the application will attempt to load and use these credentials rather than submitting a new authorisation request.
  4. As mentioned previously, we implement three methods corresponding to the three APIs we have studied. Accordingly, the official example method channels_list_by_username(client, **kwargs): is replaced by three new methods: channels_get_upload_playlist_id(client, **kwargs):, playlist_get_videos(client, **kwargs):, and videos_get_file_names(client, **kwargs):.

⓶ Listing the complete example code: Below is the content of youtube_data_api.py.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
import os
import json

import flask

import google.oauth2.credentials
import google_auth_oauthlib.flow
import googleapiclient.discovery

# The CLIENT_SECRETS_FILE variable specifies the name of a file that contains
# the OAuth 2.0 information for this application, including its client_id and
# client_secret.
CLIENT_SECRETS_FILE = "client_secret_youtube.json"

# Credentials obtained from Google Clould YouTube Data API v3.
CREDENTIALS_FILE = 'credentials_youtube.json'

# This OAuth 2.0 access scope allows for full read/write access to the
# authenticated user's account and requires requests to use an SSL connection.
SCOPES = ['https://www.googleapis.com/auth/youtube.readonly']
API_SERVICE_NAME = 'youtube'
API_VERSION = 'v3'

app = flask.Flask(__name__)
# Note: A secret key is included in the sample so that it works, but if you
# use this code in your application please replace this with a truly secret
# key. See http://flask.pocoo.org/docs/0.12/quickstart/#sessions.
app.secret_key = 'REPLACE ME - this value is here as a placeholder.'

@app.route('/')
def index():
    # Attempt to load previous credentials stored on local JSON file...
    if 'credentials' not in flask.session:    
        load_credentials()

    # There is no credentials stored on local JSON file. Try to authorise.
    if 'credentials' not in flask.session:
        return flask.redirect('authorize')

    # Load the credentials from the session.
    credentials = google.oauth2.credentials.Credentials(
        **flask.session['credentials'])

    client = googleapiclient.discovery.build(
        API_SERVICE_NAME, API_VERSION, credentials=credentials)
  
    """
    return channels_get_upload_playlist_id(client,
        part='contentDetails',
        id='UCPB9BdRkJRd3aGm9qAY3CTw')
    """

    """
    return playlist_get_videos(client,
        part='snippet,contentDetails',
        maxResults=2,
        playlistId='UUPB9BdRkJRd3aGm9qAY3CTw')
    """

    """ 
    # Note: hMG1JjBwVx4 is my own video. That is, the email address 
    #     associated with the hMG1JjBwVx4's channel is also the email
    #     address I used to obtain the OAuth 2.0 credentials.
    # 
    # ACTION ITEM for readers: 
    #     You have to replace hMG1JjBwVx4 with your own.
    #
    return videos_get_file_names(client,
        part='fileDetails',
        id='hMG1JjBwVx4')
    """

    return f'<h2>Please enable either channels_get_upload_playlist_id(...), \
            playlist_get_videos(...), or videos_get_file_names(...) call \
            to see a YouTube Data API v3 in action.</h2>'

@app.route('/authorize')
def authorize():
    # Create a flow instance to manage the OAuth 2.0 Authorization Grant Flow
    # steps.
    flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
        CLIENT_SECRETS_FILE, scopes=SCOPES)
    
    flow.redirect_uri = flask.url_for('oauth2callback', _external=True)

    authorization_url, state = flow.authorization_url(
        # This parameter enables offline access which gives your application
        # both an access and refresh token.
        access_type='offline',
        # This parameter enables incremental auth.
        include_granted_scopes='true')

    # Store the state in the session so that the callback can verify that
    # the authorization server response.
    flask.session['state'] = state

    return flask.redirect(authorization_url)

@app.route('/oauth2callback')
def oauth2callback():
    # Specify the state when creating the flow in the callback so that it can
    # verify the authorization server response.
    state = flask.session['state']
    flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
        CLIENT_SECRETS_FILE, scopes=SCOPES, state=state)
    
    flow.redirect_uri = flask.url_for('oauth2callback', _external=True)

    # Use the authorization server's response to fetch the OAuth 2.0 tokens.
    authorization_response = flask.request.url

    flow.fetch_token(authorization_response=authorization_response)

    # Store the credentials in the session.
    # ACTION ITEM for developers:
    #     Store user's access and refresh tokens in your data store if
    #     incorporating this code into your real app.
    credentials = flow.credentials

    credentials_as_dict = credentials_to_dict(credentials)

    flask.session['credentials'] = credentials_as_dict

    # Store the credentials as JSON to a file on disk.
    with open(CREDENTIALS_FILE, 'w') as f: 
        json.dump(credentials_as_dict, f)

    return flask.redirect(flask.url_for('index'))

def credentials_to_dict(credentials):
    return {'token': credentials.token,
            'refresh_token': credentials.refresh_token,
            'token_uri': credentials.token_uri,
            'client_id': credentials.client_id,
            'client_secret': credentials.client_secret,
            'scopes': credentials.scopes}

def load_credentials():
    in_error = False

    try:
        with open(CREDENTIALS_FILE) as f:
            credentials = json.load(f)
            f.close()

        keys = ['token', 'refresh_token', 'token_uri', 'client_id', 'client_secret', 'scopes']
        for key in keys:
            in_error = (key not in credentials)
            if in_error: break

    except Exception as e:
        in_error = True
    finally:
        if not in_error: 
            flask.session['credentials'] = {
                'token': credentials['token'],
                'refresh_token': credentials['refresh_token'],
                'token_uri': credentials['token_uri'],
                'client_id': credentials['client_id'],
                'client_secret': credentials['client_secret'],
                'scopes': credentials['scopes']
            }

        return in_error

def channels_get_upload_playlist_id(client, **kwargs):
    response = client.channels().list(
      **kwargs
    ).execute()

    return flask.jsonify(**response)

def playlist_get_videos(client, **kwargs):
    response = client.playlistItems().list(
      **kwargs
    ).execute()

    return flask.jsonify(**response)

def videos_get_file_names(client, **kwargs):
    response = client.videos().list(
        **kwargs
    ).execute()

    return flask.jsonify(**response)    

if __name__ == '__main__':
    # When running locally, disable OAuthlib's HTTPs verification. When
    # running in production *do not* leave this option enabled.
    os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'
    app.run('0.0.0.0', 5000, debug=True)

The example code should be self-explanatory, but let’s go over some of the key points:

● For the /oauth2callback endpoint, there are two key points:

In the section Note on OAuth 2.0 Authorized redirect URIs we mentioned configuring http://localhost:5000/oauth2callback as the value for redirect_uri. This /oauth2callback is the endpoint, and the application has been coded to listen to port 5000 at line 191: app.run('0.0.0.0', 5000, debug=True).

In the endpoint handler method oauth2callback():, lines 124 to 126:

124
125
126
    # Store the credentials as JSON to a file on disk.
    with open(CREDENTIALS_FILE, 'w') as f: 
        json.dump(credentials_as_dict, f)

This is where we write the entire granted credentials to a local JSON file as discussed in point 2 above.

● For the method load_credentials(), we attempt to load credentials from an existing credentials JSON file. Please see point 3 above.

● The default endpoint handler method index(): This is like the “main” function of this example “program”:

If the application has not been authorised yet, we attempt to load the previously acquired credentials from the local JSON file. If there are no previous credentials, or the local JSON content is in error, we attempt to request authorisation from the Google Cloud API.

We optimistically assume that one of the above steps will succeed. This is not production-ready code as stated in the introduction.

The rest of the code in this method is as per the official example. 💥 Please note that the three API call methods discussed in point 4 above have all been commented out. You will need to enable each one of them to see how they work.

⓷ To run the example application, we need to install the required packages onto the active virtual environment as per the official instructions:

pip install --upgrade google-api-python-client
pip install --upgrade google-auth google-auth-oauthlib google-auth-httplib2
pip install --upgrade flask

From the active virtual environment venv, launch the example as follows:

▶️Windows 10:venv\Scripts\python.exe youtube_data_api.py 
▶️Ubuntu 22.10:./venv/bin\python youtube_data_api.py

🚀 Then, on the same machine, access the example URL http://localhost:5000/.

For the first time requesting authorisation, the Google Cloud API will initiate the OAuth 2.0 user process, requesting the user to grant permissions. Please see the screenshots of this process below. Your experience should look similar:


The default endpoint method handler index(): was calling videos_get_file_names(...). After the user granted permissions and the OAuth 2.0 process was successful, we should have a response similar to the one illustrated below:

108-10.png

💥 Most importantly, the credentials JSON file credentials_youtube.json should have been written to the directory where youtube_data_api.py is located. Please check this for yourself.

⓸ Using the credentials_youtube.json file on another machine: For learning purposes, I copied both the youtube_data_api.py and credentials_youtube.json files to Ubuntu 22.10, set up the virtual environment, and ran the example without reconfiguring redirect_uri. The Google Cloud API happily accepted these credentials. Please see the screenshot below:

108-11.png

⓹ The method videos_get_file_names(client, **kwargs): can only work with videos which belong to our own YouTube channel. For understanding purposes, I tried to access a public video from another public channel, and below is the response:

googleapiclient.errors.HttpError: <HttpError 403 when requesting https://youtube.googleapis.com/youtube/v3/videos?part=fileDetails&id=97t-8NF7oz4&alt=json returned “The request is not properly authorized to access video file or processing information. Note that the fileDetails, processingDetails, and suggestions parts are only available to that video’s owner.”. Details: “[{‘message’: “The request is not properly authorized to access video file or processing information. Note that the fileDetails, processingDetails, and suggestions parts are only available to that video’s owner.”, ‘domain’: ‘youtube.video’, ‘reason’: ‘forbidden’, ‘location’: ‘part’, ‘locationType’: ‘parameter’}]”>

❻ Here are some helpful links for managing Google Cloud API projects.

❼ Please note: We have previously discussed the Google Gmail API in the following posts:

  1. GMail API: quick start with Python and NodeJs
  2. GMail API: send emails with NodeJs
  3. CI/CD #02. Jenkins: basic email using your Gmail account
  4. Sending emails via Gmail account using the Python module smtplib

In addition to the Google Gmail API, I have also used the Cloud Text-to-Speech API with Python. The Text-to-Speech API is comparatively easier to set up than both the Gmail API and the YouTube API.

❽ I did not intend for this post to be this long. There are just so many details to cover. Hopefully, this post will serve as a stepping stone for trying other Google Cloud APIs. I have learned a lot writing this post. And I apologise for not being able to make it shorter.

Thank you for reading. I hope you find the information in this post useful. Stay safe, as always.

✿✿✿

Feature image source: