# Creating Meeting OAuth in Python 3.7

**URL:** <https://devforum.zoom.us/t/creating-meeting-oauth-in-python-3-7/20821>\
**Category:** API and Webhooks\
**Created:** [June 9, 2020, 6:40pm UTC](https://devforum.zoom.us/t/creating-meeting-oauth-in-python-3-7/20821 "2020-06-09T18:40:03Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![bartnik.christian](https://sea2.discourse-cdn.com/flex016/user_avatar/devforum.zoom.us/bartnik.christian/32/10193_2.png) [@bartnik.christian](https://devforum.zoom.us/u/bartnik.christian)\
**Post date:** [June 9, 2020, 6:40pm UTC](https://devforum.zoom.us/t/creating-meeting-oauth-in-python-3-7/20821/1 "2020-06-09T18:40:03Z")

</div>

**Description**  
I am trying to create a meeting with OAuth with python 3.7. The below written code works perfectly for getting the meetings lists. Since the recent update, this code does not work anymore. However I am puzzled how to adopt the code and the documentation did not help very much.

**Error**  
{‘code’: 300, ‘message’: ‘Request Body should be a valid JSON object.’}

**Which App Type (OAuth / Chatbot / JWT / Webhook)?**  
OAuth

**Which Endpoint/s?**  
API endpoint: [https://api.zoom.us/v2](https://api.zoom.us/v2)

**How To Reproduce (If applicable)**

**The code is used is divided into:**

1. Utility classes and functions
2. [Zoom.us](http://Zoom.us) REST API Python Client
3. API Call to create meeting

**This is the code:**

**#1. Utility classes and functions**  
“”“Utility classes and functions”""

from **future** import absolute\_import, unicode\_literals

from urllib.parse import quote

import contextlib  
import json  
import requests  
import time  
import jwt

API\_VERSION\_2 = 2

class ApiClient(object):  
“”“Simple wrapper for REST API requests”""

```
def __init__ (self, base_uri=None, timeout=15, **kwargs):
    """Setup a new API Client
    :param base_uri: The base URI to the API
    :param timeout: The timeout to use for requests
    :param kwargs: Any other attributes. These will be added as
                       attributes to the ApiClient object.
    """
    self.base_uri = base_uri
    self.timeout = timeout
    for k, v in kwargs.items():
        setattr(self, k, v)

@property
def timeout(self):
    """The timeout"""
    return self._timeout

@timeout.setter
def timeout(self, value):
    """The default timeout"""
    if value is not None:
        try:
            value = int(value)
        except ValueError:
            raise ValueError("timeout value must be an integer")
    self._timeout = value

@property
def base_uri(self):
    """The base_uri"""
    return self._base_uri

@base_uri.setter
def base_uri(self, value):
    """The default base_uri"""
    if value and value.endswith("/"):
        value = value[:-1]
    self._base_uri = value

def url_for(self, endpoint):
    """Get the URL for the given endpoint
    :param endpoint: The endpoint
    :return: The full URL for the endpoint
    """
    if not endpoint.startswith("/"):
        endpoint = "/{}".format(endpoint)
    if endpoint.endswith("/"):
        endpoint = endpoint[:-1]
    return self.base_uri + endpoint

def get_request(self, endpoint, params=None, headers=None):
    """Helper function for GET requests
    :param endpoint: The endpoint
    :param params: The URL parameters
    :param headers: request headers
    :return: The :class:``requests.Response`` object for this request
    """
    if headers is None and self.config.get("version") == API_VERSION_2:
        headers = {"Authorization": "Bearer {}".format(self.config.get("token"))}
    return requests.get(
        self.url_for(endpoint), params=params, headers=headers, timeout=self.timeout
    )

def post_request(
    self, endpoint, params=None, data=None, headers=None, cookies=None
):
    """Helper function for POST requests
    :param endpoint: The endpoint
    :param params: The URL parameters
    :param data: The data (either as a dict or dumped JSON string) to
                 include with the POST
    :param headers: request headers
    :param cookies: request cookies
    :return: The :class:``requests.Response`` object for this request
    """
    if data and not is_str_type(data):
        data = json.dumps(data)
    if headers is None and self.config.get("version") == API_VERSION_2:
        headers = {
            "Authorization": "Bearer {}".format(self.config.get("token")),
            "Content-Type": "application/json",
        }
    return requests.post(
        self.url_for(endpoint),
        params=params,
        data=data,
        headers=headers,
        cookies=cookies,
        timeout=self.timeout,
    )

def patch_request(
    self, endpoint, params=None, data=None, headers=None, cookies=None
):
    """Helper function for PATCH requests
    :param endpoint: The endpoint
    :param params: The URL parameters
    :param data: The data (either as a dict or dumped JSON string) to
                 include with the PATCH
    :param headers: request headers
    :param cookies: request cookies
    :return: The :class:``requests.Response`` object for this request
    """
    if data and not is_str_type(data):
        data = json.dumps(data)
    if headers is None and self.config.get("version") == API_VERSION_2:
        headers = {
            "Authorization": "Bearer {}".format(self.config.get("token")),
            "Content-Type": "application/json",
        }
    return requests.patch(
        self.url_for(endpoint),
        params=params,
        data=data,
        headers=headers,
        cookies=cookies,
        timeout=self.timeout,
    )

def delete_request(
    self, endpoint, params=None, data=None, headers=None, cookies=None
):
    """Helper function for DELETE requests
    :param endpoint: The endpoint
    :param params: The URL parameters
    :param data: The data (either as a dict or dumped JSON string) to
                 include with the DELETE
    :param headers: request headers
    :param cookies: request cookies
    :return: The :class:``requests.Response`` object for this request
    """
    if data and not is_str_type(data):
        data = json.dumps(data)
    if headers is None and self.config.get("version") == API_VERSION_2:
        headers = {"Authorization": "Bearer {}".format(self.config.get("token"))}
    return requests.delete(
        self.url_for(endpoint),
        params=params,
        data=data,
        headers=headers,
        cookies=cookies,
        timeout=self.timeout,
    )

def put_request(self, endpoint, params=None, data=None, headers=None, cookies=None):
    """Helper function for PUT requests
    :param endpoint: The endpoint
    :param params: The URL paramaters
    :param data: The data (either as a dict or dumped JSON string) to
                 include with the PUT
    :param headers: request headers
    :param cookies: request cookies
    :return: The :class:``requests.Response`` object for this request
    """
    if data and not is_str_type(data):
        data = json.dumps(data)
    if headers is None and self.config.get("version") == API_VERSION_2:
        headers = {"Authorization": "Bearer {}".format(self.config.get("token"))}
    return requests.put(
        self.url_for(endpoint),
        params=params,
        data=data,
        headers=headers,
        cookies=cookies,
        timeout=self.timeout,
    )

```

@contextlib.contextmanager  
def ignored(\*exceptions):  
“”“Simple context manager to ignore expected Exceptions  
:param \*exceptions: The exceptions to safely ignore  
“””  
try:  
yield  
except exceptions:  
pass

def is\_str\_type(val):  
“”“Check whether the input is of a string type.  
We use this method to ensure python 2-3 capatibility.  
:param val: The value to check wither it is a string  
:return: In python2 it will return `True` if :attr:`val` is either an  
instance of str or unicode. In python3 it will return `True` if  
it is an instance of str  
“””  
with ignored(NameError):  
return isinstance(val, basestring)  
return isinstance(val, str)

def require\_keys(d, keys, allow\_none=True):  
“”“Require that the object have the given keys  
:param d: The dict the check  
:param keys: The keys to check :attr:`obj` for. This can either be a single  
string, or an iterable of strings  
:param allow\_none: Whether `None` values are allowed  
:raises:  
:ValueError: If any of the keys are missing from the obj  
“””  
if is\_str\_type(keys):  
keys = [keys]  
for k in keys:  
if k not in d:  
raise ValueError("’{}’ must be set".format(k))  
if not allow\_none and d[k] is None:  
raise ValueError("’{}’ cannot be None".format(k))  
return True

def date\_to\_str(d):  
“”“Convert date and datetime objects to a string  
Note, this does not do any timezone conversion.  
:param d: The :class:`datetime.date` or :class:`datetime.datetime` to  
convert to a string  
:returns: The string representation of the date  
“””  
return d.strftime("%Y-%m-%dT%H:%M:%SZ")

def generate\_jwt(key, secret):  
header = {“alg”: “HS256”, “typ”: “JWT”}

```
payload = {"iss": key, "exp": int(time.time() + 3600)}

token = jwt.encode(payload, secret, algorithm="HS256", headers=header)
return token.decode("utf-8")

```

def encode\_uuid(val):  
“”“Encode UUID as described by ZOOM API documentation  
\> Note: Please double encode your UUID when using this API if the UUID  
\> begins with a '/'or contains ‘//’ in it.  
:param val: The UUID to encode  
:returns: The encoded UUID  
“””  
if val[0] == “/” or “//” in val:  
val = quote(quote(val, safe=""), safe="")  
return val

print(“done”)

**#2. [Zoom.us](http://Zoom.us) REST API Python Client**  
“”“[Zoom.us](http://Zoom.us) REST API Python Client”""

from **future** import absolute\_import, unicode\_literals

from zoomus import components, util  
from zoomus.util import API\_VERSION\_2

API\_BASE\_URIS = {  
API\_VERSION\_2: “[https://api.zoom.us/v2](https://api.zoom.us/v2)”,  
}

COMPONENT\_CLASSES = {  
API\_VERSION\_2: {  
“user”: components.user.UserComponentV2,  
“meeting”: components.meeting.MeetingComponentV2,  
#“metric”: components.metric.MetricComponentV2,  
#“past\_meeting”: components.past\_meeting.PastMeetingComponentV2,  
“report”: components.report.ReportComponentV2,  
“webinar”: components.webinar.WebinarComponentV2,  
“recording”: components.recording.RecordingComponentV2,  
#“phone”: components.phone.PhoneComponentV2,  
},  
}

class ZoomClient(util.ApiClient):  
“”“[Zoom.us](http://Zoom.us) REST API Python Client”""

```
"""Base URL for Zoom API"""

def __init__ (
    self, api_key, api_secret, data_type="json", timeout=15, version=API_VERSION_2
):
    """Create a new Zoom client
    :param api_key: The Zooom.us API key
    :param api_secret: The Zoom.us API secret
    :param data_type: The expected return data type. Either 'json' or 'xml'
    :param timeout: The time out to use for API requests
    """
    try:
        BASE_URI = API_BASE_URIS[version]
        self.components = COMPONENT_CLASSES[version].copy()
    except KeyError:
        raise RuntimeError("API version not supported: %s" % version)

    super(ZoomClient, self). __init__ (base_uri=BASE_URI, timeout=timeout)

    # Setup the config details
    self.config = {
        "api_key": api_key,
        "api_secret": api_secret,
        "data_type": data_type,
        "version": version,
        "token": util.generate_jwt(api_key, api_secret),
    }

    # Instantiate the components
    for key in self.components.keys():
        self.components[key] = self.components[key](
            base_uri=BASE_URI, config=self.config
        )

def __enter__ (self):
    return self

def __exit__ (self, exc_type, exc_val, exc_tb):
    return

def refresh_token(self):
    self.config["token"] = (
        util.generate_jwt(self.config["api_key"], self.config["api_secret"]),
    )

@property
def api_key(self):
    """The Zoom.us api_key"""
    return self.config.get("api_key")

@api_key.setter
def api_key(self, value):
    """Set the api_key"""
    self.config["api_key"] = value
    self.refresh_token()

@property
def api_secret(self):
    """The Zoom.us api_secret"""
    return self.config.get("api_secret")

@api_secret.setter
def api_secret(self, value):
    """Set the api_secret"""
    self.config["api_secret"] = value
    self.refresh_token()

@property
def meeting(self):
    """Get the meeting component"""
    return self.components.get("meeting")

@property
def metric(self):
    """Get the metric component"""
    return self.components.get("metric")

@property
def report(self):
    """Get the report component"""
    return self.components.get("report")

@property
def user(self):
    """Get the user component"""
    return self.components.get("user")

@property
def webinar(self):
    """Get the webinar component"""
    return self.components.get("webinar")

@property
def recording(self):
    """Get the recording component"""
    return self.components.get("recording")

@property
def phone(self):
    """Get the phone component"""
    return self.components.get("phone")

```

print(“done”)

**#3. API Call to create meeting**

import datetime  
import json

client = ZoomClient(‘api\_key’, ‘api\_sec’)

user\_list\_response = client.user.list()  
user\_list = json.loads(user\_list\_response.content)

datetime\_object = datetime.datetime.strptime(“2020-03-21 20:00:00+00:00”[:19], ‘%Y-%m-%d %H:%M:%S’)  
datetime\_object = datetime\_object.strftime(’%Y-%m-%dT%H:%M:%SZ’)  
datetime\_object = datetime.datetime.strptime(datetime\_object, ‘%Y-%m-%dT%H:%M:%SZ’)

for user in user\_list[‘users’]:  
user\_id = user[‘id’]  
#print(json.loads(client.meeting.list(user\_id=user\_id).content)) #THIS WORKS  
print(json\_file = json.loads(client.meeting.create(user\_id=user\_id, host\_id=“host\_id”, duration=60, timezone=“Europe/Vienna”, topic=“Sophie & Chris”, type=“2”, start\_time=datetime\_object).content))

**Additional context**  
Can anybody please help with the current code or provide a code snippet for this problem? Thank you!

---

<div class="post-metadata">

**Author:** ![tommy](https://sea2.discourse-cdn.com/flex016/user_avatar/devforum.zoom.us/tommy/32/72769_2.png) [@tommy](https://devforum.zoom.us/u/tommy)\
**Post date:** [June 12, 2020, 10:58pm UTC](https://devforum.zoom.us/t/creating-meeting-oauth-in-python-3-7/20821/2 "2020-06-12T22:58:07Z")

</div>

Hey @bartnik.christian,

It seems like your request body JSON is invalid. You can please send me your request body in JSON format so I can debug?

Thanks,  
Tommy

---

<div class="post-metadata">

**Author:** ![DeveloperBot](https://sea2.discourse-cdn.com/flex016/user_avatar/devforum.zoom.us/developerbot/32/12632_2.png) [@DeveloperBot](https://devforum.zoom.us/u/DeveloperBot)\
**Post date:** [August 21, 2020, 10:02pm UTC](https://devforum.zoom.us/t/creating-meeting-oauth-in-python-3-7/20821/3 "2020-08-21T22:02:24Z")

</div>

Closed on August 21, 2020, 10:02pm UTC


