(Failed) trying to get recently played songs from Apple Music API
Tags: api, applemusic, apple, music
Published: 2021-03-14 17:11:51

Recently I was on a quest to get recently played songs from Apple Music.

##Background

Over the past few months, I've made [MoovinGroovin](https://www.moovingroovin.com/), a web service that creates playlists from the songs you listened when working out with Strava turned on.

MoovinGroovin is integrated with Spotify, and I got a request from a user to add support for Apple Music.

Apple Music has a rest API (Apple Music API). Getting the tokens to access Apple Music API is a whole story on its own deserving a separate post, which you can find here:

{% post jurooravec/reverse-engineering-apple-s-musickitjs-to-create-apple-music-strategy-for-passportjs-12h %}

In this one instead, I focus on what I've learnt about the Apple Music API's endpoint that [fetches recently played resources](https://developer.apple.com/documentation/applemusicapi/get_recently_played_resources):

> https://api.music.apple.com/v1/me/recent/played

## TL;DR:

- The endpoint returns only **playlists**, **albums**, and **stations**. If you need **songs** like I did, you're out of luck.
- The endpoint remembers **only the last 50 recently played resources** (same as Spotify). If you need older info, you better start caching darling.
- You can get **at most 10 items per request**. You can specify the limit with `limit=n` query param.
- To get resources beyond the first 10 items, you can use `offset=n` query param. If there are more data available beyond what you requested, the response also has a `"next"` key with URL to the next offset (e.g. `"/v1/me/recent/played?offset=40"`).
- The endpoint returns the resources in the order that they've been played. _However_, you don't get the _time at which the resource was started_. Which is what I needed. And it made me sad.

---

## What I did

#### Observation 1: If you set `limit` above `10`, you get an error in the payload.

This confirms we have to operate in the range of 1-10 resources per request.

{% details Response for "https://api.music.apple.com/v1/me/recent/played?limit=11" %} 


```json
{
    "errors": [
        {
            "id": "X6VOUROLIVE5XJSVKIA5FFHQEI",
            "title": "Invalid Parameter Value",
            "detail": "Value must be an integer less than or equal to 10",
            "status": "400",
            "code": "40005",
            "source": {
                "parameter": "limit"
            }
        }
    ]
}
```

{% enddetails %}

Just out of curiosity, `limit` that's less than 1 (so 0 and including negatives) also throws.

#### Observation 2: `offset` DOES work for this endpoint

Other endpoints accept `offset` query param, so I tried I here too. Good news, offset works!

At the time of testing, I had just 4 entries in recently played. That was expected, today was the first time I've used Apple Music.

{% details Response for "https://api.music.apple.com/v1/me/recent/played" %} 

```json
{
    "data": [
        {
            "id": "ra.562939683",
            "type": "stations",
            "href": "/v1/catalog/sk/stations/ra.562939683",
            "attributes": {
                "isLive": false,
                "artwork": {
                    "width": 2400,
                    "height": 2400,
                    "url": "https://is5-ssl.mzstatic.com/image/thumb/Features114/v4/40/0d/22/400d2226-ab71-66f4-92a1-baaa07276664/mzl.wimxogea.png/{w}x{h}AM.RSAB01.jpeg",
                    "bgColor": "f3f3f3",
                    "textColor1": "040404",
                    "textColor2": "282828",
                    "textColor3": "333333",
                    "textColor4": "505050"
                },
                "url": "https://music.apple.com/sk/station/rone-similar-artists/ra.562939683",
                "playParams": {
                    "id": "ra.562939683",
                    "kind": "radioStation",
                    "format": "tracks",
                    "stationHash": "CgkIARoFo463jAIQAQ"
                },
                "name": "Rone & Similar Artists"
            }
        },
        {
            "id": "pl.41fced032b8c4a909de7c696566ed485",
            "type": "playlists",
            "href": "/v1/catalog/sk/playlists/pl.41fced032b8c4a909de7c696566ed485",
            "attributes": {
                "artwork": {
                    "width": 1080,
                    "height": 1080,
                    "url": "https://is1-ssl.mzstatic.com/image/thumb/Features114/v4/87/09/5e/87095e3e-df15-05d8-6baa-4bf32ea6c0e0/source/{w}x{h}SC.DN01.jpeg?l=en-GB",
                    "bgColor": "aadf95",
                    "textColor1": "000000",
                    "textColor2": "0e2149",
                    "textColor3": "1c3656",
                    "textColor4": "264157"
                },
                "isChart": false,
                "url": "https://music.apple.com/sk/playlist/indie-electronic/pl.41fced032b8c4a909de7c696566ed485",
                "lastModifiedDate": "2021-03-12T05:00:46Z",
                "name": "Indie Electronic",
                "playlistType": "editorial",
                "curatorName": "Apple Music Indie",
                "playParams": {
                    "id": "pl.41fced032b8c4a909de7c696566ed485",
                    "kind": "playlist"
                },
                "description": {
                    "standard": "The longer we live and listen in this digital world, the weirder it feels to talk about electronic music. Still, it’s the artists that have embraced the possibilities of electronics on their own terms that have turned out the most reliably interesting music, hybrids that combine classic songwriting and expressivity—be it rock, soul, indie or pop—with sounds and forms that didn’t exist until the programming did. From the downtempo, club-inflected indie pop of artists like The xx to the straight-up cybernetic visions of FKA twigs, here’s a playlist gathering the most exciting indie electronic music of the still-young millennium. Our editors update tracks here regularly, so if you hear something you like, add it to your library.",
                    "short": "All hail the hybrids."
                }
            }
        },
        {
            "id": "1551178998",
            "type": "albums",
            "href": "/v1/catalog/sk/albums/1551178998",
            "attributes": {
                "artwork": {
                    "width": 4000,
                    "height": 4000,
                    "url": "https://is1-ssl.mzstatic.com/image/thumb/Music114/v4/fc/7f/0a/fc7f0a77-66a6-1a3a-018a-315d6f5d3eca/190295042998.jpg/{w}x{h}bb.jpeg",
                    "bgColor": "040404",
                    "textColor1": "fafafa",
                    "textColor2": "e0e2e1",
                    "textColor3": "c9c9c9",
                    "textColor4": "b4b6b5"
                },
                "artistName": "Dua Lipa",
                "isSingle": false,
                "url": "https://music.apple.com/sk/album/future-nostalgia-the-moonlight-edition/1551178998",
                "isComplete": true,
                "genreNames": [
                    "Pop",
                    "Music"
                ],
                "trackCount": 19,
                "isMasteredForItunes": false,
                "releaseDate": "2021-02-11",
                "name": "Future Nostalgia (The Moonlight Edition)",
                "recordLabel": "Warner Records",
                "upc": "190295042998",
                "copyright": "A Warner Records UK Release, ℗ 2021 Dua Lipa Limited under exclusive licence to Warner Music UK Limited. Except tracks 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 18 ℗ 2020, tracks 1 & 2 ℗ 2019 Dua Lipa Limited under exclusive licence to Warner Music UK Limited, track 14  ℗ 2020 RCA Records, a division of Sony Music Entertainment, track 19 ℗ 2020 Sueños Globales, LLC, Exclusively Licensed to UMG Recordings Inc, ℗ Universal Music Latino/NEON16.",
                "playParams": {
                    "id": "1551178998",
                    "kind": "album"
                },
                "editorialNotes": {
                    "standard": "When Dua Lipa released <i>Future Nostalgia</i> in March 2020, she knew she was taking a risk (in a tearful social media video at the time, the British singer wondered whether releasing an album so laced with joy was “the right thing to do” just as a global pandemic hit). Her gamble, of course, paid off. <i>Future Nostalgia</i>—a celebratory odyssey into pop’s future, influenced by the late-night sounds of its past—proved to be just the tonic a world in crisis needed. “When I was creating this record, I wanted it to feel like a form of escapism,” Lipa tells Apple Music. “And it felt like, during this time, it did serve as a form of escapism for me. And I was so happy with the response that the record got, and people [who] were like, ‘This got me through lockdown.’” Almost exactly a year later—and following an exhilarating, star-studded remix album, a Mercury Prize nomination and six Grammy Award nods—comes <i>The Moonlight Edition</i>. Here, you’ll find the original record's 11 uplifting tracks and the stream of hits Lipa served up in its wake (“Fever” with Angèle, a DaBaby-featuring remix of “Levitating” and “Prisoner”, Lipa’s inescapable Miley Cyrus collab) plus previously unreleased tracks, including “We’re Good”, on which Lipa departs from <i>Future Nostalgia</i>’s iridescent, disco-indebted sounds. <i>The Moonlight Edition</i> celebrates an album that met the moment—and allowed its creator to conquer the world.",
                    "short": "Pop’s unstoppable force delivers another round of Future Nostalgia."
                },
                "isCompilation": false,
                "contentRating": "explicit"
            }
        },
        {
            "id": "pl.886b4cfed21c4a1c9d817491feaeb8aa",
            "type": "playlists",
            "href": "/v1/catalog/sk/playlists/pl.886b4cfed21c4a1c9d817491feaeb8aa",
            "attributes": {
                "artwork": {
                    "width": 1080,
                    "height": 1080,
                    "url": "https://is1-ssl.mzstatic.com/image/thumb/Features124/v4/d9/fe/ca/d9feca92-73e1-c44e-386f-cfae36749aaf/source/{w}x{h}SC.CAESS02.jpeg?l=en-GB",
                    "bgColor": "21162a",
                    "textColor1": "ffffff",
                    "textColor2": "e8dcdc",
                    "textColor3": "cabdba",
                    "textColor4": "baadb3"
                },
                "isChart": false,
                "url": "https://music.apple.com/sk/playlist/inspirational-songs-essentials/pl.886b4cfed21c4a1c9d817491feaeb8aa",
                "lastModifiedDate": "2021-01-29T23:03:43Z",
                "name": "Inspirational Songs Essentials",
                "playlistType": "editorial",
                "curatorName": "Apple Music Pop",
                "playParams": {
                    "id": "pl.886b4cfed21c4a1c9d817491feaeb8aa",
                    "kind": "playlist"
                },
                "description": {
                    "standard": "Inspirational songs come in all shapes, sizes, and genres. You’ll encounter celebratory hard rock mingling with defiant hip-hop, peak-hour EDM rubbing shoulders with bubbly pop. There are even plenty of ballads as fragile as fine china. What’s more, the songs we find inspiring have many different purposes. More than a few are like rocket fuel powering us through sweaty workouts or late nights on the town; others have more personal aims, like pulling folks out of heartache or self-doubt. Some possess political dimensions, serving as soundtracks for social movements or pleas for peace and love in a world racked with strife. Yet all of them share a few vital traits. In addition to lyrics focused on perseverance and overcoming obstacles, each one boasts a riff, rhythm, or lead vocal that sounds as though it’s capable of physically elevating listeners to higher planes of existence.",
                    "short": "Beloved anthems of perseverance and overcoming obstacles."
                }
            }
        }
    ]
}
```

{% enddetails %}

When I set the `limit=2`, I got just 2 entries. As expected.

{% details Response for "https://api.music.apple.com/v1/me/recent/played?limit=2" %} 

```json
{
    "next": "/v1/me/recent/played?offset=2",
    "data": [
        {
            "id": "ra.562939683",
            "type": "stations",
            "href": "/v1/catalog/sk/stations/ra.562939683",
            "attributes": {
                "playParams": {
                    "id": "ra.562939683",
                    "kind": "radioStation",
                    "format": "tracks",
                    "stationHash": "CgkIARoFo463jAIQAQ"
                },
                "isLive": false,
                "artwork": {
                    "width": 2400,
                    "height": 2400,
                    "url": "https://is5-ssl.mzstatic.com/image/thumb/Features114/v4/40/0d/22/400d2226-ab71-66f4-92a1-baaa07276664/mzl.wimxogea.png/{w}x{h}AM.RSAB01.jpeg",
                    "bgColor": "f3f3f3",
                    "textColor1": "040404",
                    "textColor2": "282828",
                    "textColor3": "333333",
                    "textColor4": "505050"
                },
                "url": "https://music.apple.com/sk/station/rone-similar-artists/ra.562939683",
                "name": "Rone & Similar Artists"
            }
        },
        {
            "id": "pl.41fced032b8c4a909de7c696566ed485",
            "type": "playlists",
            "href": "/v1/catalog/sk/playlists/pl.41fced032b8c4a909de7c696566ed485",
            "attributes": {
                "artwork": {
                    "width": 1080,
                    "height": 1080,
                    "url": "https://is1-ssl.mzstatic.com/image/thumb/Features114/v4/87/09/5e/87095e3e-df15-05d8-6baa-4bf32ea6c0e0/source/{w}x{h}SC.DN01.jpeg?l=en-GB",
                    "bgColor": "aadf95",
                    "textColor1": "000000",
                    "textColor2": "0e2149",
                    "textColor3": "1c3656",
                    "textColor4": "264157"
                },
                "isChart": false,
                "url": "https://music.apple.com/sk/playlist/indie-electronic/pl.41fced032b8c4a909de7c696566ed485",
                "lastModifiedDate": "2021-03-12T05:00:46Z",
                "name": "Indie Electronic",
                "playlistType": "editorial",
                "curatorName": "Apple Music Indie",
                "playParams": {
                    "id": "pl.41fced032b8c4a909de7c696566ed485",
                    "kind": "playlist"
                },
                "description": {
                    "standard": "The longer we live and listen in this digital world, the weirder it feels to talk about electronic music. Still, it’s the artists that have embraced the possibilities of electronics on their own terms that have turned out the most reliably interesting music, hybrids that combine classic songwriting and expressivity—be it rock, soul, indie or pop—with sounds and forms that didn’t exist until the programming did. From the downtempo, club-inflected indie pop of artists like The xx to the straight-up cybernetic visions of FKA twigs, here’s a playlist gathering the most exciting indie electronic music of the still-young millennium. Our editors update tracks here regularly, so if you hear something you like, add it to your library.",
                    "short": "All hail the hybrids."
                }
            }
        }
    ]
}
```

{% enddetails %}

Then I tried to get "next page", so `limit=2&offset=2`. And lo and behold, I got the next page!

{% details Response for "https://api.music.apple.com/v1/me/recent/played?limit=2&offset=2" %} 

```json
{
    "data": [
        {
            "id": "1551178998",
            "type": "albums",
            "href": "/v1/catalog/sk/albums/1551178998",
            "attributes": {
                "artwork": {
                    "width": 4000,
                    "height": 4000,
                    "url": "https://is1-ssl.mzstatic.com/image/thumb/Music114/v4/fc/7f/0a/fc7f0a77-66a6-1a3a-018a-315d6f5d3eca/190295042998.jpg/{w}x{h}bb.jpeg",
                    "bgColor": "040404",
                    "textColor1": "fafafa",
                    "textColor2": "e0e2e1",
                    "textColor3": "c9c9c9",
                    "textColor4": "b4b6b5"
                },
                "artistName": "Dua Lipa",
                "isSingle": false,
                "url": "https://music.apple.com/sk/album/future-nostalgia-the-moonlight-edition/1551178998",
                "isComplete": true,
                "genreNames": [
                    "Pop",
                    "Music"
                ],
                "trackCount": 19,
                "isMasteredForItunes": false,
                "releaseDate": "2021-02-11",
                "name": "Future Nostalgia (The Moonlight Edition)",
                "recordLabel": "Warner Records",
                "upc": "190295042998",
                "copyright": "A Warner Records UK Release, ℗ 2021 Dua Lipa Limited under exclusive licence to Warner Music UK Limited. Except tracks 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 18 ℗ 2020, tracks 1 & 2 ℗ 2019 Dua Lipa Limited under exclusive licence to Warner Music UK Limited, track 14  ℗ 2020 RCA Records, a division of Sony Music Entertainment, track 19 ℗ 2020 Sueños Globales, LLC, Exclusively Licensed to UMG Recordings Inc, ℗ Universal Music Latino/NEON16.",
                "playParams": {
                    "id": "1551178998",
                    "kind": "album"
                },
                "editorialNotes": {
                    "standard": "When Dua Lipa released <i>Future Nostalgia</i> in March 2020, she knew she was taking a risk (in a tearful social media video at the time, the British singer wondered whether releasing an album so laced with joy was “the right thing to do” just as a global pandemic hit). Her gamble, of course, paid off. <i>Future Nostalgia</i>—a celebratory odyssey into pop’s future, influenced by the late-night sounds of its past—proved to be just the tonic a world in crisis needed. “When I was creating this record, I wanted it to feel like a form of escapism,” Lipa tells Apple Music. “And it felt like, during this time, it did serve as a form of escapism for me. And I was so happy with the response that the record got, and people [who] were like, ‘This got me through lockdown.’” Almost exactly a year later—and following an exhilarating, star-studded remix album, a Mercury Prize nomination and six Grammy Award nods—comes <i>The Moonlight Edition</i>. Here, you’ll find the original record's 11 uplifting tracks and the stream of hits Lipa served up in its wake (“Fever” with Angèle, a DaBaby-featuring remix of “Levitating” and “Prisoner”, Lipa’s inescapable Miley Cyrus collab) plus previously unreleased tracks, including “We’re Good”, on which Lipa departs from <i>Future Nostalgia</i>’s iridescent, disco-indebted sounds. <i>The Moonlight Edition</i> celebrates an album that met the moment—and allowed its creator to conquer the world.",
                    "short": "Pop’s unstoppable force delivers another round of Future Nostalgia."
                },
                "isCompilation": false,
                "contentRating": "explicit"
            }
        },
        {
            "id": "pl.886b4cfed21c4a1c9d817491feaeb8aa",
            "type": "playlists",
            "href": "/v1/catalog/sk/playlists/pl.886b4cfed21c4a1c9d817491feaeb8aa",
            "attributes": {
                "artwork": {
                    "width": 1080,
                    "height": 1080,
                    "url": "https://is1-ssl.mzstatic.com/image/thumb/Features124/v4/d9/fe/ca/d9feca92-73e1-c44e-386f-cfae36749aaf/source/{w}x{h}SC.CAESS02.jpeg?l=en-GB",
                    "bgColor": "21162a",
                    "textColor1": "ffffff",
                    "textColor2": "e8dcdc",
                    "textColor3": "cabdba",
                    "textColor4": "baadb3"
                },
                "isChart": false,
                "url": "https://music.apple.com/sk/playlist/inspirational-songs-essentials/pl.886b4cfed21c4a1c9d817491feaeb8aa",
                "lastModifiedDate": "2021-01-29T23:03:43Z",
                "name": "Inspirational Songs Essentials",
                "playlistType": "editorial",
                "curatorName": "Apple Music Pop",
                "playParams": {
                    "id": "pl.886b4cfed21c4a1c9d817491feaeb8aa",
                    "kind": "playlist"
                },
                "description": {
                    "standard": "Inspirational songs come in all shapes, sizes, and genres. You’ll encounter celebratory hard rock mingling with defiant hip-hop, peak-hour EDM rubbing shoulders with bubbly pop. There are even plenty of ballads as fragile as fine china. What’s more, the songs we find inspiring have many different purposes. More than a few are like rocket fuel powering us through sweaty workouts or late nights on the town; others have more personal aims, like pulling folks out of heartache or self-doubt. Some possess political dimensions, serving as soundtracks for social movements or pleas for peace and love in a world racked with strife. Yet all of them share a few vital traits. In addition to lyrics focused on perseverance and overcoming obstacles, each one boasts a riff, rhythm, or lead vocal that sounds as though it’s capable of physically elevating listeners to higher planes of existence.",
                    "short": "Beloved anthems of perseverance and overcoming obstacles."
                }
            }
        }
    ]
}
```

{% enddetails %}

Then I also looked at what happens if you set `offset` beyond what's in the recently-played list. As expected, I got empty array.

{% details Response for "https://api.music.apple.com/v1/me/recent/played?offset=10" %} 

```json
{
    "data": []
}
```

{% enddetails %}

The `offset` can be set to as high as `1000000000`s (10^9). At `10000000000` (one more zero), the query param is ignored.

Negative offset is ignored.

---

## Conclusion

Frankly, I had higher hopes for the Apple Music API. While getting 50 latest recently played resources is fine, these are the cons:

- No `playedAt` info per resource. One album might play just few seconds and one for hours, and you won't know.
- No songs in the response. You don't know **what exact songs** were played.
- **Obtaining music user token is difficult**, if you want to make requests to personalized endpoints on the server-side. I made a passportJS strategy for just that, reverse engineering [MusicKitJS](https://developer.apple.com/documentation/musickitjs/), but it wasn't easy. ~~Still need to publish it on NPM.~~ Now published as [passport-apple-music](https://www.npmjs.com/package/passport-apple-music).
- If you're unfamiliar with Apple's developer env, it's hard to know which service ID and with what permissions you need to access Apple Music API. It took me 3 days to realize I was integrating "Sign in with Apple" instead of Apple Music access.

---

## References

Apple Music API Docs: https://developer.apple.com/documentation/applemusicapi/get_recently_played_resources
Spotify API Docs: https://developer.spotify.com/documentation/web-api/reference/#endpoint-get-the-users-currently-playing-track
Photo by [Kind and Curious](https://unsplash.com/@kindandcurious?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/s/photos/failure?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)