1

Using Spotify Documentation for Client Credential Flow here:

I was able to create a API request in google app script (Javascript).

  function callAPI () {

    SPOTIFY_CLIENT_SECRET = secret
    SPOTIFY_CLIENT_ID = id

    const HEADERS = {
      "Content-Type": "application/json",
      'Authorization': `Basic ${Utilities.base64Encode(SPOTIFY_CLIENT_ID + ':' + SPOTIFY_CLIENT_SECRET)})`
      }
    
    const BODY = {
      'grant_type': 'client_credentials'
      }
    
    var url = `https://api.spotify.com/api/token`

    var requestOptions = {
      'method': 'POST',
      'headers': HEADERS,
      'payload': JSON.stringify(BODY),
      'muteHttpExceptions': true,
      'redirect': 'follow'
      };

    var response = UrlFetchApp.fetch(url, requestOptions);
    var data = JSON.parse(response.getContentText());

I am confused about two things, please be able to answer both.

1). The Spotify documentation states to enter "Basic" before the client credentials in the authorization header.

Client Credential Image

Yet, when I run this code, I get this error

{ error: 
   { status: 400,
     message: 'Only valid bearer authentication supported' } }

If, I'm am using client credential flow, why does it think I am using a bearer token? (Also if I change authentication to Bearer I get a 401 error "Invalid access token")

2). Could you provide an example of a working version of this code and why it was able to run opposed to mine?

  • Did you encode the client id& client secret? It says `Authorization: Basic ` , so probably you should encode them. – Reut Schremer Sep 30 '21 at 14:10
  • Utilities.base64Encode(SPOTIFY_CLIENT_ID + ':' + SPOTIFY_OAUTH_TOKEN) This method is used by google app script to encode using base 64 – Mario Perry Sep 30 '21 at 14:16
  • Why you passing oauth token instead of client secret? – Kos Sep 30 '21 at 14:17
  • SPOTIFY_OAUTH_TOKEN? it should be the secret, no? – Reut Schremer Sep 30 '21 at 14:19
  • My apologies, that was just what I named the variable at the time, the oauth token variable has the client secret. I'm going to edit this so it reflects the changes – Mario Perry Sep 30 '21 at 14:19
  • try using just `'payload': BODY` according to your documentation link: > The body of this POST request must contain the following parameters **encoded in application/x-www-form-urlencoded** – Kos Sep 30 '21 at 14:44
  • That didn't work, it still throws the same error – Mario Perry Sep 30 '21 at 14:58

2 Answers2

1

I believe your goal is as follows.

  • You want to convert the following curl command to Google Apps Script.

      curl -X "POST" -H "Authorization: Basic ZjM4ZjAw...WY0MzE=" -d grant_type=client_credentials https://accounts.spotify.com/api/token
    

In this case, grant_type=client_credentials is sent as the form data. When I saw your script, it is sent as the data. And you use the URL of https://api.spotify.com/api/token. But the curl command uses https://accounts.spotify.com/api/token. `I thought that these might be the reason for your issue. So when your script is modified, it becomes as follows.

Modified script:

function callAPI() {
  SPOTIFY_CLIENT_SECRET = secret; // Please set your value.
  SPOTIFY_CLIENT_ID = id; // Please set your value.
  const HEADERS = {
    'Authorization': `Basic ${Utilities.base64Encode(SPOTIFY_CLIENT_ID + ':' + SPOTIFY_CLIENT_SECRET)}` // Modified
  }
  const BODY = {
    'grant_type': 'client_credentials'
  }
  var url = "https://accounts.spotify.com/api/token";
  var requestOptions = {
    'method': 'POST',
    'headers': HEADERS,
    'payload': BODY,
    'muteHttpExceptions': true,
  };
  var response = UrlFetchApp.fetch(url, requestOptions);
  var data = response.getContentText();
  console.log(data)
}

Note:

  • When I saw your script again, I noticed that Basic ${Utilities.base64Encode(SPOTIFY_CLIENT_ID + ':' + SPOTIFY_CLIENT_SECRET)}) is required to be modified. Because in this case, it's Basic ###). Please remove ).

References:

Tanaike
  • 181,128
  • 11
  • 97
  • 165
  • This is soo close. I think the catch of sending it to the wrong URL was why it was coming up with the Bearer token issue (because it was the wrong endpoint.) However, I'm still getting an error - {"error":"invalid_client"}. This time there is no status code, just that exact text. I wonder if it has something to do with the authorization header. – Mario Perry Oct 01 '21 at 04:28
  • @Mario Perry When I saw your script again, I noticed that `'Authorization': `Basic ${Utilities.base64Encode(SPOTIFY_CLIENT_ID + ':' + SPOTIFY_CLIENT_SECRET)})`` is required to be modified. Because in this case, it's `Basic ###)`. Please remove `)`. I thought that your error of `{"error":"invalid_client"}` is due to this. So I updated my answer. Could you please confirm it? If the error occurs again, at that time, please check your client ID and client secret again. – Tanaike Oct 01 '21 at 04:35
  • this works too! I think we both found solutions to the same problem at similar times! Thank You – Mario Perry Oct 01 '21 at 04:46
  • @Mario Perry Thank you for replying. I'm glad your issue was resolved. – Tanaike Oct 01 '21 at 04:47
1

I figured it out! For some reason you need to add the client id and client secret in the form data. The Spotify docs says to put them in the headers base64 encoded but that is not the case in this instance. (You don't even need to encode them)

Also you don't even need to include the content-type parameter like the doc says.

working code looks like this

  function callAPI () {
    let SPOTIFY_CLIENT_SECRET = secret
    let SPOTIFY_CLIENT_ID = id

  const BODY = {
    'Content-Type': 'application/x-www-form-urlencoded',
    'grant_type': 'client_credentials',
    "client_id": SPOTIFY_CLIENT_ID,
    "client_secret": SPOTIFY_CLIENT_SECRET,
  }

  var url = "https://accounts.spotify.com/api/token";
  
  var requestOptions = {
    'method': 'POST',
    'payload': BODY,
    'muteHttpExceptions': true,
  };

  var response = UrlFetchApp.fetch(url, requestOptions);
  var data = response.getContentText();
  console.log(data)

  }

I found my answer from reading about a similar problem here