Introduction
# 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:** ```bash osascript -e 'tell application "Music" to playpause' osascript -e 'tell application "Music" to return name of current track' ```
**Multi-line scripts:** ```bash 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)
```applescript 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)
```applescript 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
```applescript 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
```applescript 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
```applescript 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
```applescript 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
```applescript 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: ```python 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:** 1. Apple Developer account 2. MusicKit key (.p8 file) from [developer portal](https://developer.apple.com/account/resources/authkeys/list) 3. Developer token (JWT, 180 day max) 4. User music token (browser OAuth)
**Generate developer token:** ```python 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
```bash 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:
```python 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
```bash POST /v1/me/library/playlists Content-Type: application/json
{ "attributes": { "name": "Road Trip", "description": "Summer vibes" }, "relationships": { "tracks": { "data": [] } } } ```
## Ratings
```bash # 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:** ```python # WRONG json={"data": [{"id": "1234567890", "type": "songs"}]} ``` **Fix:** Add to library first, get library ID, then add.
**❌ Playing catalog songs via AppleScript:** ```applescript # WRONG play track id "1234567890" ``` **Fix:** Song must be in library.
**❌ Unescaped AppleScript strings:** ```python # 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](https://github.com/epheterson/mcp-applemusic) MCP server handles all this complexity automatically: AppleScript escaping, token management, library-first workflow, ID conversions.
**Install:** ```bash 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:** ```json { "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 |