/oauth/token 400 Bad Request (params in query string)

API Endpoint(s)
/oauth/token

Description
Hello everyone,
we have an integration with the Zoom meetings API which uses the account_credentials grant type to get an Access Toke via a Server-to-Server app in order to use the Meetings API to create/update meetings etc.

Error?
We recently started getting 400 Bad Request response to /oauth/token:

400 Client Error: Bad Request for url: https://zoom.us/oauth/token?grant_type=account_credentials&account_id=[REDACTED]

Further details
Our code sends the grant_type and account_id params in the query string of the POST request to the /oauth/token (the code was written a few years ago).

A quick check of the docs reveals we should be

  • putting the params in the request body
  • set the Content-Type header as application/x-www-form-urlencoded

I checked the changelog and couldn’t see recent changes about the /token endpoint.
Has this recently changed? Or was the query string params an undocumented feature that has recently been tightened?

Thank you

Hello everyone,

I see you’re facing an issue with Zoom’s Meetings API integration using the account_credentials grant type for obtaining an Access Token via a Server-to-Server app.

The Problem

You’re encountering a 400 Bad Request error when making a request to the /oauth/token endpoint:

400 Client Error: Bad Request for url: https://zoom.us/oauth/token?grant_type=account_credentials&account_id=[REDACTED]

This happens if your code sends the grant_type and account_id parameters in the query string of the POST request. The correct method is to:

  • Send the parameters in the request body
  • Set the Content-Type header to application/x-www-form-urlencoded

Solution

Here’s a working NodeJS implementation to fix the issue:

const axios = require("axios");

class Rest {
    static async getHeaders() {
        const { data, status } = await this.getAccessToken();

        return {
            authorization: `Bearer ${data.access_token}`,
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        }
    }

    static normalizeUrl(url) {
        const parsedUrl = new URL(url);
        return parsedUrl.toString();
    }

    static async request(config, retry = 2) {
        try {
            config.url = await this.normalizeUrl(config.url);
            config.headers = await this.getHeaders();

            return axios(config)
                .then(function (response) {
                    return response;
                })
                .catch(async function (error) {
                    const { response } = error;
                    const { data, status } = response;
                    const { message } = data;

                    return { message, status };
                });
        } catch (error) {
            console.log(`-- Error while request, error message: ${error?.message}`);
        }
    }

    static async getAccessToken() {
        try {
            // Zoom account details
            const accountID = process.env.ZOOM_ACCOUNT_ID;
            const clientId = process.env.ZOOM_CLIENT_ID;
            const clientSecret = process.env.ZOOM_CLIENT_SECRET;

            // Encode client ID and secret in Base64
            const authToken = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');

            // Configure the request
            const config = {
                method: 'post',
                url: 'https://zoom.us/oauth/token',
                params: {
                    grant_type: 'account_credentials',
                    account_id: accountID,
                },
                headers: {
                    'Host': 'zoom.us',
                    'Authorization': `Basic ${authToken}`,
                },
            };

            // Make the request
            return await axios(config)
                .then((response) => {
                    return response;
                })
                .catch((error) => {
                    const { response } = error;
                    return response;
                });
        } catch (error) {
            console.log(`-- Error while get access token, error message: ${error?.message}`);
        }
    }
}

module.exports = {
    Rest,
};

Key Fixes Implemented:

  1. Moved parameters to the request body using params in Axios.
  2. Implemented proper Basic Authentication with Base64 encoding.
  3. Added error handling for better debugging and logging.

I hope this solution helps you resolve the issue.

Let me know if you have any questions!

Thank you!

1 Like