Search the iTunes catalog from Swift.
A Swift concurrency client for Apple's iTunes Search API. Typed entity and media searches, validated request options, and a per-client rate limit.
Quick start
import TunesSearchKit
let response = try await TunesClient.shared.song("Indigo - Sam Barber")
for song in response.results {
print(song.trackName)
}
Every search returns a TunesResponse<T>,
where T is the model for that entity. Calls
are async and throws,
so use them from an async context or a Task.
Look up by ID
When you already know a catalog identifier, look up an item by its
iTunes ID, AMG ID, UPC/EAN, or ISBN. Related entity lookups can return
mixed result shapes, which are represented by
LookupResult.
let response = try await TunesClient.shared.lookup(
.amgArtistId([468749, 5723]),
entity: .album,
limit: 5,
sort: .recent
)
for result in response.results {
if case .album(let album) = result {
print(album.collectionName)
}
}
Install
Requires Swift 6.2 or later, and iOS 16 or later or macOS 13 or later.
Add the package to your Package.swift:
dependencies: [
.package(url: "https://github.com/YasserB94/TunesSearchKit.git", branch: "main")
]
Then add the product to your target:
.product(name: "TunesSearchKit", package: "tunessearchkit")
What you get
Typed entity search
Use entity or its convenience methods,
like song or
software, when you want one concrete
result type.
Media search
Use media, all,
music, or tv to
search a whole category and switch over mixed results.
Built-in rate limiting
Each client allows up to 20 requests per minute. It throws
TunesError.rateLimited(seconds) when its
limit is reached.
Validated requests
Bad parameters throw
TunesError.invalidParameter before any
request is sent.
Supported typed entity queries
| Descriptor | Result type |
|---|---|
.song |
Song |
.album |
Album |
.musicArtist |
MusicArtist |
.musicTrack |
MusicTrack (song or music video)
|
.musicVideo |
MusicVideo |
.podcast |
Podcast |
.audiobook |
Audiobook |
.tvEpisode,
.tvSeason
|
TVEpisode,
TVSeason
|
.software |
Software |
.ebook |
Ebook |
Apple documents additional entities, including movie, movieArtist,
shortFilm, shortFilmArtist, podcastAuthor, audiobookAuthor, and mix.
These entity searches returned no results during fixture generation,
so TunesSearchKit does not currently provide typed entity queries for
them. The feature-movie kind is supported
inside AllTrack when returned by an all-media
search.
Media searches and result types
Media searches decode each response row into the result enum shown below. The enum cases reflect the response shapes handled by the current decoders.
| Media query | Result type and supported cases |
|---|---|
.all |
AllMedia: album, artist, podcast,
music video, audiobook, TV season, and
AllTrack results. AllTrack can
decode songs, music videos, podcasts, feature movies, and TV
episodes.
|
.music |
MusicMedia: music artist, album, and
MusicTrack (song or music video).
|
.musicVideo |
MusicVideoMedia: music artist or
music video.
|
.podcast |
PodcastMedia: podcast. |
.audiobook |
AudiobookMedia: audiobook. |
.tv |
TVMedia: TV episode or season.
|
.software |
SoftwareMedia: software. iPad and
Mac software use the same result case.
|
.ebook |
EbookMedia: ebook. |
For example, an all-media result may be an album or artist directly,
or a track-shaped result whose AllTrack case
identifies the concrete model:
let response = try await TunesClient.shared.all("Faithless", limit: 10)
for result in response.results {
switch result {
case .album(let album):
print("Album: \(album.collectionName)")
case .allArtist(let artist):
print("Artist: \(artist.artistName)")
case .allTrack(let track):
switch track {
case .song(let song): print("Song: \(song.trackName)")
case .musicVideo(let video): print("Video: \(video.trackName)")
case .podcast(let podcast): print("Podcast: \(podcast.trackName)")
case .featureMovie(let movie): print("Movie: \(movie.trackName ?? "Untitled")")
case .tvEpisode(let episode): print("Episode: \(episode.trackName)")
@unknown default:
break
}
default:
break
}
}
Request options
let response = try await TunesClient.shared.song(
"Faithless",
country: "BE",
limit: 10,
explicit: false
)
| Option | Default | Valid values |
|---|---|---|
country |
US |
Two-letter country code |
limit |
50 |
1...200 |
lang |
en_us |
en_us, ja_jp
|
version |
2 |
1, 2
|
explicit |
true |
true, false
|
Use it in SwiftUI
struct SongList: View {
let query: String
@State private var songs: [Song] = []
var body: some View {
List(songs, id: \.trackId) { song in
Text(song.trackName)
}
.task {
songs = (try? await TunesClient.shared.song(query).results) ?? []
}
}
}
Go deeper
The full API reference covers every model, the media search enums, and all error cases. The contributing guide covers the repository layout, running tests and lint locally, and generating fixtures.