MSTR Prime
MSTR Prime

Reputation: 41

How to call Apple Music API and avoid 401

I am persistently getting 401 error when trying to call the Apple Music API. I am using a windows machine and below is the Python Code for it.


import datetime
import jwt

secret = """-----BEGIN PRIVATE KEY-----
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxx
-----END PRIVATE KEY-----"""

kid = 'ABCDEFGHI'
teamId = 'JKLMNOPQRS'
alg = 'ES256'

headers = {
    'alg': alg,
    'kid': kid
}

payload = {
    'iss': teamId,
    'iat': 1518033023,
    'exp': 1518290267
}

if __name__ == '__main__':
    '''Create an auth token'''
    token = jwt.encode(payload, secret, algorithm=alg, headers=headers)

print '----CURL----'
print ("curl -v -H 'Authorization: Bearer %s' \"https://api.music.apple.com/v1/catalog/us/playlists/pl.14362d3dfe4b41f7878939782647e0ba\" " % (token))

Upvotes: 4

Views: 1763

Answers (4)

AzeTech
AzeTech

Reputation: 707

The Apple Music API requires both developer token and user token. Although the question is regarding python, this answer is meant for swift developers who come across this issue in future..

For example,

static func addPlaylistPathURLString(developerToken: String, userToken: String) -> URLRequest {
         var urlComponents = URLComponents()
         urlComponents.scheme = "https"
         urlComponents.host = "api.music.apple.com"
         urlComponents.path = "/v1/me/library/playlists/

         // Create and configure the `URLRequest`.

         var urlRequest = URLRequest(url: urlComponents.url!)
         urlRequest.httpMethod = "GET"

         urlRequest.addValue("Bearer \(developerToken)", forHTTPHeaderField: "Authorization")
         urlRequest.addValue(userToken, forHTTPHeaderField: "Music-User-Token")

         return urlRequest
     }

     func addPlaylistPathURLString(with developertoken: String, userToken: String, completion: @escaping CatalogSearchCompletionHandler) {

 guard let developerToken = refreshDeveloperToken() // its developer token 
else {
           fatalError("Developer Token not configured. See README for more details.")
             }
            let urlRequest = AppleMusicRequestFactory.addPlaylistPathURLString(developerToken: developerToken, userToken: userToken)
            print(urlRequest)

             let task = urlSession.dataTask(with: urlRequest) { (data, response, error) in
                 guard error == nil, let urlResponse = response as? HTTPURLResponse, urlResponse.statusCode == 200 else {
                     completion([], error)

                     return
                 }
                 guard
                    let data = data,
                   let json = String(data: data, encoding: .utf8)

                else { return }
                print("json:", json)
               do {
                    // do your stuffs---

                  } catch {

                 print("An error occurred: \(error.localizedDescription)")
                 }
             }

             task.resume()

        }

Regarding how to get user token from developer token,

func requestUserToken() {
        guard let developerToken = appleMusicManager.refreshDeveloperToken() // its developer token 
   else {
            return
        }

        if SKCloudServiceController.authorizationStatus() == .authorized {

            let completionHandler: (String?, Error?) -> Void = { [weak self] (token, error) in
                guard error == nil else {
                    print("An error occurred when requesting user token: \(error!.localizedDescription)")
                    return
                }

                guard let token = token else {
                    print("Unexpected value from SKCloudServiceController for user token.")
                    return
                }

                self?.userToken = token
                print(token)

                /// Store the Music User Token for future use in your application.
                let userDefaults = UserDefaults.standard

                userDefaults.set(token, forKey: AuthorizationManager.userTokenUserDefaultsKey)
                userDefaults.synchronize()

                if self?.cloudServiceStorefrontCountryCode == "" {
                    self?.requestStorefrontCountryCode()
                }

                NotificationCenter.default.post(name: AuthorizationManager.cloudServiceDidUpdateNotification, object: nil)
            }

            if #available(iOS 11.0, *) {
                cloudServiceController.requestUserToken(forDeveloperToken: developerToken, completionHandler: completionHandler)
            } else {
                cloudServiceController.requestPersonalizationToken(forClientToken: developerToken, withCompletionHandler: completionHandler)
            }
        }
    }

Upvotes: 0

barbecu
barbecu

Reputation: 742

I had the same problem, For me my .p8 file had the key on multiple lines when it should've been on just one:

-----BEGIN PRIVATE KEY-----
notarealkey5GSM49AgEGCCqGSM49AwEHBHkwdwIBAQQguWRXMHYkuFImkMGByqEPT
jaXQyO0WK1BjYpuDxIgNQ5nHRRFCuUOi8mgCgYIKoZIzj0DAQehcp0+Z+jwRANCAA
RCBFg8fL08QS36Fb8HmY+eFrDWMO00w5unCo5n8VyLhvttIZeByXlVsJrK/L3f/
F2wYmZme
-----END PRIVATE KEY-----

vs:

-----BEGIN PRIVATE KEY-----
asdfg1rty5GSM49AgEGCCqGSM49AwEHBHkwdwI...
-----END PRIVATE KEY-----

Upvotes: 0

sanwall
sanwall

Reputation: 63

I had the same problem with the string formatting operator. Therefore use this:

"""Create an auth token"""
token = jwt.encode(payload, secret, algorithm=alg, headers=headers)
token_str = token.decode('utf-8')  # converts bytes to string

url = "https://api.music.apple.com/v1/catalog/us/playlists/pl.14362d3dfe4b41f7878939782647e0ba" 

print("curl -v -H 'Authorization: Bearer" + token_str + "\"" + url + "\"")

Further: I assume you want to access the data directly. For this you can use the requests library:

import requests
request_obj = requests.get(url, headers={'Authorization': "Bearer " + token_str})
json_dict = request_obj.json()

Cheers!

Upvotes: 3

petezurich
petezurich

Reputation: 10184

Adding to sanwall's answer – this works for me (Python 3.x):

from datetime import datetime
import jwt
import requests
import json

secret_key = '''-----BEGIN PRIVATE KEY-----
... your secret key here ...
-----END PRIVATE KEY-----'''

key_id  = '0123456789' # <-- your key id here
team_id = '0123456789' # <-- your team id here
alg     = 'ES256'
iat     = int(datetime.utcnow().strftime("%s")) # set issued at to now
exp     = iat + 86400 # add e.g. 24h from now for expiration (24 * 3600secs == 86400)

payload = {
    'iss': team_id,
    'iat': iat,
    'exp': exp
}

headers = {
    'alg': alg,
    'kid': key_id
}

token = jwt.encode(payload, secret_key, algorithm=alg, headers=headers)
token_str = token.decode('utf-8')

url = "https://api.music.apple.com/v1/catalog/us/songs/203709340"
print (f"curl -v -H 'Authorization: Bearer {token_str}' {url}")

Or use requests with the generated token string:

res = requests.get(url, headers={'Authorization': "Bearer " + token_str})
result = json.loads(res.text)

Printout:

{'data': [{'id': '203709340', 'type': 'songs', 'href': '/v1/catalog/us/songs/203709340', ... etc ...

Upvotes: 1

Related Questions