MusanovaKit lets you explore Apple Music features that are not exposed through the public MusicKit framework. It includes helpers for private APIs such as privileged lyric endpoints and Music Summaries (Replay) data. Use this package for research and internal tooling only.
⚠️ MusanovaKit calls Apple Music endpoints that require a privileged developer token. Shipping these APIs inside production software is likely to violate Apple’s terms and may break without notice. Proceed at your own risk.
- Requirements
- Installation
- What's in 4.0.0
- Authentication
- Concerts
- Lyrics
- Replay and Summaries
- Library Pins
- Taste Preferences
- Editorial Rooms
- Social Relationships
- Sample App
- Disclaimer
- Swift 6.0 or later
- Xcode 16 or later
- An Apple Developer account with access to Apple Music privileged developer tokens
Add MusanovaKit to your project using the Swift Package Manager:
dependencies: [
.package(url: "https://github.com/rryam/MusanovaKit.git", from: "4.0.0")
]Then add MusanovaKit to the target that should use these APIs.
Version 4 adds typed clients and decoding models for Apple Music's concert hub, editorial rooms, taste preferences, social profiles, Replay milestones, and library pin mutations. Lyrics now expose raw TTML, line-level text, and timed syllable segments; the parser uses Apple's literal TTML whitespace to distinguish word boundaries from adjacent timed syllables.
The repository also includes a signed macOS sample app. It turns the responses into a concert browser, a Replay timeline, a pin shelf, and a synchronized lyrics player with clickable seeking.
All MusanovaKit calls depend on Apple Music authentication. There are two tokens involved:
Obtain a privileged Apple Music developer token from your internal tooling and provide it to MusanovaKit (for example through the DEVELOPER_TOKEN environment variable or by injecting it directly into API calls).
When you call MusanovaKit from a signed-in MusicKit app, the system automatically attaches the Music User Token to privileged requests. If you issue requests from outside MusicKit (for example, using curl), include the Media-User-Token header manually.
Load Apple Music's concert hub by storefront. Filters support a geohash, calendar dates, genres, selected sections, and per-section limits.
let options = ConcertHubOptions(
geoHashLocation: "gcpvj",
dateRange: ConcertDateRange(
startDate: "2026-07-10",
endDate: "2026-07-31"
),
genreIDs: ["14"]
)
let hub = try await MConcerts.hub(
storefront: "gb",
developerToken: token,
options: options
)
for section in hub.orderedContainers {
print(section.title ?? "Concerts")
section.data.forEach { print($0.attributes.name) }
}The detail response includes venues, coordinates, postal addresses, artists, related playlists, ticket vendors, and the data providers behind the event.
let concert = try await MConcerts.concert(
id: "ce.example",
storefront: "gb",
developerToken: token
)
print(concert.attributes.name)
print(concert.relationships?.venues?.data.first?.attributes.name ?? "No venue")An artist lookup returns the complete upcoming view. Pass a geohash to request Apple's nearby view in the same response.
let concerts = try await MConcerts.upcomingConcerts(
forArtistID: "1462541757",
storefront: "gb",
geoHashLocation: "gcpvj",
limit: 8,
developerToken: token
)
print(concerts.all.data.count)
print(concerts.nearby?.data.count ?? 0)MusanovaKit can return the original TTML, parsed paragraphs, or a flat list of timed segments. MCatalog.lyrics(for:developerToken:) remains available and returns the parsed paragraphs.
let song = try await MCatalog.song(id: "1156786545")
let lyrics = try await MCatalog.lyrics(for: song, developerToken: token)
for paragraph in lyrics {
if let part = paragraph.songPart {
print("\n\(part.uppercased()):")
}
for line in paragraph.lines {
print(line.text)
}
}lyrics returns an array of LyricParagraph objects. Each paragraph contains:
songPart– optional section labeling (Intro, Verse, Chorus, …)lines– an array ofLyricLine
Every LyricLine now exposes:
text– aggregated plain text for the linesegments–[LyricSegment]representing the timed spans inside the line
Each LyricSegment includes the displayed text and startTime / endTime as TimeInterval values derived from the TTML <span begin="…" end="…"> attributes.
let paragraph = lyrics.first
let line = paragraph?.lines.first
line?.segments.forEach { segment in
print("\(segment.text) starts at \(segment.startTime)s, ends at \(segment.endTime)s")
}Use the segment timings to drive your own lyric highlighting, karaoke bars, or subtitle cues. Segments default to a startTime of 0 if the TTML omits the begin attribute.
Fetch the segments directly when paragraph and line grouping isn't needed:
let segments = try await MCatalog.timedLyrics(for: song, developerToken: token)
for segment in segments {
print("\(segment.startTime): \(segment.text)")
}MCatalog.parsedLyrics(for:developerToken:countryCode:) returns the grouped paragraphs explicitly. Both methods accept an optional storefront country code; without one, MusicKit resolves the current storefront.
Use rawLyrics when you need Apple Music's original document:
let ttml = try await MCatalog.rawLyrics(for: song, developerToken: token)To decode the complete resource, including its playback parameters, call lyricsResponse instead.
If you need the raw TTML, instantiate MusicLyricsRequest directly:
let request = MusicLyricsRequest(songID: MusicItemID("1156786545"), developerToken: token)
let response = try await request.response()
let ttml = response.data.first?.attributes.ttmlMusicLyricsRequest automatically targets the /syllable-lyrics endpoint and decodes the MusicLyricsResponse payload.
You can also parse TTML obtained elsewhere. parse returns an empty array for malformed input, while parseValidating throws MusanovaKitError.invalidResponseFormat:
let paragraphs = try LyricsParser().parseValidating(ttml)
let segments = paragraphs.timedSegmentsMusanovaKit also contains helpers for Replay (Music Summaries) endpoints. These APIs return insights such as top artists, albums, songs, and milestone achievements for a user’s listening history.
let milestones = try await MSummaries.milestones(forYear: 2023, developerToken: token)
for milestone in milestones {
print("\(milestone.kind): reached on \(milestone.dateReached)")
print("Listen time: \(milestone.listenTimeInMinutes) minutes")
}let results = try await MSummaries.search(developerToken: token)
for summary in results {
print("Year: \(summary.year) → playlist: \(summary.playlist)")
}MusanovaKit provides access to the user's pinned items in their Apple Music library. Pinned items represent content that users have marked as favorites or important, and may appear prominently in the Apple Music interface.
let response = try await MLibrary.pins(developerToken: token, limit: 25)
for pinRef in response.data {
print("Pinned: \(pinRef.id) - Type: \(pinRef.type)")
}
// Access detailed resources
if let albums = response.resources?.libraryAlbums {
for (id, album) in albums {
print("Album: \(album.attributes?.name ?? "Unknown") by \(album.attributes?.artistName ?? "Unknown")")
}
}
if let songs = response.resources?.librarySongs {
for (id, song) in songs {
print("Song: \(song.attributes?.name ?? "Unknown") by \(song.attributes?.artistName ?? "Unknown")")
}
}The pins() method returns a MusicLibraryPinsResponse containing:
data: Array of pin references with basic informationresources: Detailed information about pinned items organized by type
For full control over the request parameters, create a configuration object and pass it to the method:
var configuration = MusicLibraryPinsRequest(developerToken: token)
configuration.limit = 50
configuration.language = "es-ES"
configuration.librarySongIncludes = ["albums", "artists"]
configuration.libraryArtistIncludes = ["catalog"]
let response = try await MLibrary.pins(configuration: configuration)
// Access the raw response data
for pinRef in response.data {
print("Pinned: \(pinRef.id) of type \(pinRef.type)")
}Pin and unpin albums, songs, artists, and playlists to/from the user's Apple Music library:
// Pin items
let album: Album = // ... fetched album
try await MLibrary.pin(album, developerToken: token)
let song: Song = // ... fetched song
try await MLibrary.pin(song, developerToken: token)
let playlist: Playlist = // ... fetched playlist
try await MLibrary.pin(playlist, developerToken: token)
let artist: Artist = // ... fetched artist
try await MLibrary.pin(artist, developerToken: token)
// Unpin items
try await MLibrary.unpin(album, developerToken: token)
try await MLibrary.unpin(song, developerToken: token)
try await MLibrary.unpin(playlist, developerToken: token)
try await MLibrary.unpin(artist, developerToken: token)All Album, Song, Playlist, and Artist types conform to the Pinnable protocol, allowing them to be used with the pin(_:developerToken:) and unpin(_:developerToken:) methods.
Pins can be moved after another pin or straight to the top. Albums and playlists can also use play or shuffle as their default action.
try await MLibrary.movePin(
withID: pinID,
afterPinWithID: precedingPinID,
developerToken: token
)
try await MLibrary.movePinToTop(withID: pinID, developerToken: token)
try await MLibrary.setPlaybackAction(
.shuffle,
forPinWithID: pinID,
developerToken: token
)The Pinnable protocol is defined as:
/// A protocol that represents a music item that can be pinned to the user's Apple Music library.
///
/// This protocol includes the requirement that items must be music items that can be identified
/// and pinned through Apple's private API endpoints.
public protocol Pinnable: MusicItem {}Available configuration options:
limit: Maximum number of pins to return (default: 25)language: Language/locale for the request (default: "en-GB")librarySongIncludes: Relationships to include for songslibraryArtistIncludes: Relationships to include for artistslibraryMusicVideoIncludes: Relationships to include for music videos
Fetch the resources Apple Music uses when personalizing recommendations for the current user:
let preferences = try await MTaste.preferences(developerToken: token)
for preference in preferences {
print("\(preference.attributes.name): \(preference.attributes.preference)")
}Use MusicTastePreferencesRequest directly when you need the unresolved references and resource map from TastePreferencesResponse.
Apple Music uses rooms and multirooms to build editorial pages. A room holds catalog resources such as artists, albums, and playlists. A multiroom combines text sections, catalog content, links to child rooms, and hero artwork.
let room = try await MEditorial.room(
id: "room-id",
storefront: "us",
developerToken: token
)
for item in room.contents {
print("\(item.type): \(item.attributes?.name ?? item.id)")
}
let page = try await MEditorial.multiroom(
id: "multiroom-id",
storefront: "us",
developerToken: token
)
for section in page.children {
print(section.attributes?.title ?? section.id)
}EditorialContentResource keeps the resource type, identifier, and common display fields instead of assuming every item has the same catalog shape. Unknown resource types still decode, although their type-specific fields aren't modeled.
Apple Music protects follower, followee, and pending-follower reads with an action signature in addition to the developer and user tokens. MusanovaKit asks AppleMediaServices to add that signature before sending these requests.
let profile = try await MSocial.profile(developerToken: token)
let followers = try await MSocial.followers(developerToken: token)
let followees = try await MSocial.followees(developerToken: token)
let pending = try await MSocial.pendingFollowers(developerToken: token)An account with no matching profiles returns an empty profiles array. For another endpoint that uses the same signature gate, opt in through MusicPrivilegedDataRequest:
let request = MusicPrivilegedDataRequest(
url: url,
developerToken: token,
requiresActionSignature: true
)This signer depends on a private system framework and can stop working after an OS update. Treat it as macOS research tooling, not production or App Store code.
Open Musanova/Musanova.xcodeproj, select your development team, and use a bundle identifier with MusicKit enabled. Paste the AMP developer token into the app's Settings tab; the repository does not contain a token.
The macOS app includes Concerts, Replay, Lyrics, Pins, and Settings tabs. The lyrics screen uses ApplicationMusicPlayer, follows line timing from TTML, seeks when a line is clicked, and keeps the current line centered while playback advances.
MusanovaKit is an exploration project. These endpoints are subject to change, can disappear without warning, and may violate Apple’s App Store policies. Do not submit apps that rely on this package to the App Store. Use the code for prototyping, research, or personal experiments only.