apple-music
Apple Music Integration
Guide for integrating with Apple Music. Covers AppleScript (macOS), MusicKit API (cross-platform), and the critical library-first requirement.
When to Use
Invoke when users ask to:
- Manage playlists (create, add/remove tracks, list)
- Control playback (play, pause, skip, volume)
- Search catalog or library
- Add songs to library
- Access listening history or recommendations
Critical Rule: Library-First Workflow
You CANNOT add catalog songs directly to playlists.
Songs must be in the user's library first:
- ❌ Catalog ID → Playlist (fails)
- ✅ Catalog ID → Library → Playlist (works)
Why: Playlists use library IDs (i.abc123), not catalog IDs (1234567890).
This applies to both AppleScript and API approaches.
Platform Comparison
| Feature | AppleScript (macOS) | MusicKit API |
|---|---|---|
| Setup required | None | Dev account + tokens |
| Playlist management | Full | API-created only |
| Playback control | Full | None |
| Catalog search | No | Yes |
| Library access | Instant | With tokens |
| Cross-platform | No | Yes |
AppleScript (macOS)
Zero setup. Works immediately with the Music app.
Run via Bash:
osascript -e 'tell application "Music" to playpause'
osascript -e 'tell application "Music" to return name of current track'
Multi-line scripts:
osascript <<'EOF'
tell application "Music"
set t to current track
return {name of t, artist of t}
end tell
EOF
Available Operations
| Category | Operations |
|---|---|
| Playback | play, pause, stop, resume, next track, previous track, fast forward, rewind |
| Player State | player position, player state, sound volume, mute, shuffle enabled/mode, song repeat |
| Current Track | name, artist, album, duration, time, rating, loved, disliked, genre, year, track number |
| Library | search, list tracks, get track properties, set ratings |
| Playlists | list, create, delete, rename, add tracks, remove tracks, get tracks |
| AirPlay | list devices, select device, current device |
Track Properties (Read)
tell application "Music"
set t to current track
-- Basic info
name of t -- "Hey Jude"
artist of t -- "The Beatles"
album of t -- "1 (Remastered)"
album artist of t -- "The Beatles"
composer of t -- "Lennon-McCartney"
genre of t -- "Rock"
year of t -- 1968
-- Timing
duration of t -- 431.0 (seconds)
time of t -- "7:11" (formatted)
start of t -- start time in seconds
finish of t -- end time in seconds
-- Track info
track number of t -- 21
track count of t -- 27
disc number of t -- 1
disc count of t -- 1
-- Ratings
rating of t -- 0-100 (20 per star)
loved of t -- true/false
disliked of t -- true/false
-- Playback
played count of t -- 42
played date of t -- date last played
skipped count of t -- 3
skipped date of t -- date last skipped
-- IDs
persistent ID of t -- "ABC123DEF456"
database ID of t -- 12345
end tell
Track Properties (Writable)
tell application "Music"
set t to current track
set rating of t to 80 -- 4 stars
set loved of t to true
set disliked of t to false
set name of t to "New Name" -- rename track
set genre of t to "Alternative"
set year of t to 1995
end tell
Player State Properties
tell application "Music"
player state -- stopped, playing, paused, fast forwarding, rewinding
player position -- current position in seconds (read/write)
sound volume -- 0-100 (read/write)
mute -- true/false (read/write)
shuffle enabled -- true/false (read/write)
shuffle mode -- songs, albums, groupings
song repeat -- off, one, all (read/write)
current track -- track object
current playlist -- playlist object
current stream URL -- URL if streaming
end tell
Playback Commands
tell application "Music"
-- Play controls
play -- play current selection
pause
stop
resume
playpause -- toggle play/pause
next track
previous track
fast forward
rewind
-- Play specific content
play (first track of library playlist 1 whose name contains "Hey Jude")
play user playlist "Road Trip"
-- Settings
set player position to 60 -- seek to 1:00
set sound volume to 50 -- 0-100
set mute to true
set shuffle enabled to true
set song repeat to all -- off, one, all
end tell
Library Queries
tell application "Music"
-- All library tracks
every track of library playlist 1
-- Search by name
tracks of library playlist 1 whose name contains "Beatles"
-- Search by artist
tracks of library playlist 1 whose artist contains "Beatles"
-- Search by album
tracks of library playlist 1 whose album contains "Abbey Road"
-- Combined search
tracks of library playlist 1 whose name contains "Hey" and artist contains "Beatles"
-- By genre
tracks of library playlist 1 whose genre is "Rock"
-- By year
tracks of library playlist 1 whose year is 1969
-- By rating
tracks of library playlist 1 whose rating > 60 -- 3+ stars
-- Loved tracks
tracks of library playlist 1 whose loved is true
-- Recently played (sort by played date)
tracks of library playlist 1 whose played date > (current date) - 7 * days
end tell
Playlist Operations
tell application "Music"
-- List all playlists
name of every user playlist
-- Get playlist
user playlist "Road Trip"
first user playlist whose name contains "Road"
-- Create playlist
make new user playlist with properties {name:"New Playlist", description:"My playlist"}
-- Delete playlist
delete user playlist "Old Playlist"
-- Rename playlist
set name of user playlist "Old Name" to "New Name"
-- Get playlist tracks
every track of user playlist "Road Trip"
name of every track of user playlist "Road Trip"
-- Add track to playlist (must be library track)
set targetPlaylist to user playlist "Road Trip"
set targetTrack to first track of library playlist 1 whose name contains "Hey Jude"
duplicate targetTrack to targetPlaylist
-- Remove track from playlist
delete (first track of user playlist "Road Trip" whose name contains "Hey Jude")
-- Playlist properties
duration of user playlist "Road Trip" -- total duration
time of user playlist "Road Trip" -- formatted duration
count of tracks of user playlist "Road Trip"
end tell
AirPlay
tell application "Music"
-- List AirPlay devices
name of every AirPlay device
-- Get current device
current AirPlay devices
-- Set output device
set current AirPlay devices to {AirPlay device "Living Room"}
-- Multiple devices
set current AirPlay devices to {AirPlay device "Living Room", AirPlay device "Kitchen"}
-- Device properties
set d to AirPlay device "Living Room"
name of d
kind of d -- computer, AirPort Express, Apple TV, AirPlay device, Bluetooth device
active of d -- true if playing
available of d -- true if reachable
selected of d -- true if in current devices
sound volume of d -- 0-100
end tell
String Escaping
Always escape user input:
def escape_applescript(s):
return s.replace('\\', '\\\\').replace('"', '\\"')
safe_name = escape_applescript(user_input)
script = f'tell application "Music" to play user playlist "{safe_name}"'
Limitations
- No catalog access - only library content
- macOS only - no Windows/Linux
MusicKit API
Cross-platform but requires Apple Developer account ($99/year) and token setup.
Authentication
Requirements:
- Apple Developer account
- MusicKit key (.p8 file) from developer portal
- Developer token (JWT, 180 day max)
- User music token (browser OAuth)
Generate developer token:
import jwt, datetime
with open('AuthKey_XXXXXXXXXX.p8') as f:
private_key = f.read()
token = jwt.encode(
{
'iss': 'TEAM_ID',
'iat': int(datetime.datetime.now().timestamp()),
'exp': int((datetime.datetime.now() + datetime.timedelta(days=180)).timestamp())
},
private_key,
algorithm='ES256',
headers={'alg': 'ES256', 'kid': 'KEY_ID'}
)
Get user token: Browser OAuth to https://authorize.music.apple.com/woa
Headers for all requests:
Authorization: Bearer {developer_token}
Music-User-Token: {user_music_token}
Base URL: https://api.music.apple.com/v1
Available Endpoints
Catalog (Public - dev token only)
| Endpoint | Method | Description |
|---|---|---|
/catalog/{storefront}/search |
GET | Search songs, albums, artists, playlists |
/catalog/{storefront}/songs/{id} |
GET | Song details |
/catalog/{storefront}/albums/{id} |
GET | Album details |
/catalog/{storefront}/albums/{id}/tracks |
GET | Album tracks |
/catalog/{storefront}/artists/{id} |
GET | Artist details |
/catalog/{storefront}/artists/{id}/albums |
GET | Artist's albums |
/catalog/{storefront}/artists/{id}/songs |
GET | Artist's top songs |
/catalog/{storefront}/artists/{id}/related-artists |
GET | Similar artists |
/catalog/{storefront}/playlists/{id} |
GET | Playlist details |
/catalog/{storefront}/charts |
GET | Top charts |
/catalog/{storefront}/genres |
GET | All genres |
/catalog/{storefront}/search/suggestions |
GET | Search autocomplete |
/catalog/{storefront}/stations/{id} |
GET | Radio station |
Library (Requires user token)
| Endpoint | Method | Description |
|---|---|---|
/me/library/songs |
GET | All library songs |
/me/library/albums |
GET | All library albums |
/me/library/artists |
GET | All library artists |
/me/library/playlists |
GET | All library playlists |
/me/library/playlists/{id} |
GET | Playlist details |
/me/library/playlists/{id}/tracks |
GET | Playlist tracks |
/me/library/search |
GET | Search library |
/me/library |
POST | Add to library |
/catalog/{sf}/songs/{id}/library |
GET | Get library ID from catalog ID |
Playlist Management
| Endpoint | Method | Description |
|---|---|---|
/me/library/playlists |
POST | Create playlist |
/me/library/playlists/{id}/tracks |
POST | Add tracks to playlist |
Personalization
| Endpoint | Method | Description |
|---|---|---|
/me/recommendations |
GET | Personalized recommendations |
/me/history/heavy-rotation |
GET | Frequently played |
/me/recent/played |
GET | Recently played |
/me/recent/added |
GET | Recently added |
Ratings
| Endpoint | Method | Description |
|---|---|---|
/me/ratings/songs/{id} |
GET | Get song rating |
/me/ratings/songs/{id} |
PUT | Set song rating |
/me/ratings/songs/{id} |
DELETE | Remove rating |
/me/ratings/albums/{id} |
GET/PUT/DELETE | Album ratings |
/me/ratings/playlists/{id} |
GET/PUT/DELETE | Playlist ratings |
Storefronts
| Endpoint | Method | Description |
|---|---|---|
/storefronts |
GET | All storefronts |
/storefronts/{id} |
GET | Storefront details |
/me/storefront |
GET | User's storefront |
Common Query Parameters
| Parameter | Description | Example |
|---|---|---|
term |
Search query | term=beatles |
types |
Resource types | types=songs,albums |
limit |
Results per page (max 25) | limit=10 |
offset |
Pagination offset | offset=25 |
include |
Related resources | include=artists,albums |
extend |
Additional attributes | extend=editorialNotes |
l |
Language code | l=en-US |
Search Example
GET /v1/catalog/us/search?term=wonderwall&types=songs&limit=10
Response:
{
"results": {
"songs": {
"data": [{
"id": "1234567890",
"type": "songs",
"attributes": {
"name": "Wonderwall",
"artistName": "Oasis",
"albumName": "(What's the Story) Morning Glory?",
"durationInMillis": 258773,
"releaseDate": "1995-10-02",
"genreNames": ["Alternative", "Music"]
}
}]
}
}
}
Library-First Workflow (Complete)
Adding a catalog song to a playlist requires 4 API calls:
import requests
headers = {
"Authorization": f"Bearer {dev_token}",
"Music-User-Token": user_token
}
# 1. Search catalog
r = requests.get(
"https://api.music.apple.com/v1/catalog/us/search",
headers=headers,
params={"term": "Wonderwall Oasis", "types": "songs", "limit": 1}
)
catalog_id = r.json()['results']['songs']['data'][0]['id']
# 2. Add to library
requests.post(
"https://api.music.apple.com/v1/me/library",
headers=headers,
params={"ids[songs]": catalog_id}
)
# 3. Get library ID (catalog ID → library ID)
r = requests.get(
f"https://api.music.apple.com/v1/catalog/us/songs/{catalog_id}/library",
headers=headers
)
library_id = r.json()['data'][0]['id']
# 4. Add to playlist (library IDs only!)
requests.post(
f"https://api.music.apple.com/v1/me/library/playlists/{playlist_id}/tracks",
headers={**headers, "Content-Type": "application/json"},
json={"data": [{"id": library_id, "type": "library-songs"}]}
)
Create Playlist
POST /v1/me/library/playlists
Content-Type: application/json
{
"attributes": {
"name": "Road Trip",
"description": "Summer vibes"
},
"relationships": {
"tracks": {
"data": []
}
}
}
Ratings
# Love a song (value: 1 = love, -1 = dislike)
PUT /v1/me/ratings/songs/{id}
Content-Type: application/json
{"attributes": {"value": 1}}
Limitations
- No playback control - API cannot play/pause/skip
- Playlist editing - can only modify API-created playlists
- Token management - dev tokens expire every 180 days
- Rate limits - Apple enforces request limits
Common Mistakes
❌ Using catalog IDs in playlists:
# WRONG
json={"data": [{"id": "1234567890", "type": "songs"}]}
Fix: Add to library first, get library ID, then add.
❌ Playing catalog songs via AppleScript:
# WRONG
play track id "1234567890"
Fix: Song must be in library.
❌ Unescaped AppleScript strings:
# WRONG
name = "Rock 'n Roll"
script = f'tell application "Music" to play playlist "{name}"'
Fix: Escape quotes.
❌ Expired tokens: Dev tokens last 180 days max. Fix: Check expiration, handle 401 errors.
The Easy Way: mcp-applemusic
The mcp-applemusic MCP server handles all this complexity automatically: AppleScript escaping, token management, library-first workflow, ID conversions.
Install:
git clone https://github.com/epheterson/mcp-applemusic.git
cd mcp-applemusic && python3 -m venv venv && source venv/bin/activate
pip install -e .
Configure Claude Desktop:
{
"mcpServers": {
"Apple Music": {
"command": "/path/to/mcp-applemusic/venv/bin/python",
"args": ["-m", "applemusic_mcp"]
}
}
}
On macOS, most features work immediately. For catalog features or Windows/Linux, see the repo README.
| Manual | mcp-applemusic |
|---|---|
| 4 API calls to add song | playlist(action="add", auto_search=True) |
| AppleScript escaping | Automatic |
| Token management | Automatic with warnings |