Apple Music API
The TrueScrape Apple Music API exposes 4 public endpoints covering Album details, Artist details, Search and Track details. Every call is a GET against public, logged-out pages and returns the same unified schema as every other platform here. Calls cost 1 credit each. A cache hit is free, and a failed or empty response is never charged.
Endpoints
| Endpoint | Returns | Credits | Cacheable | Batchable |
|---|---|---|---|---|
| /v1/apple-music/album | Album details | 1 | yes | yes |
| /v1/apple-music/artist | Artist details | 1 | yes | yes |
| /v1/apple-music/search | Search | 1 | yes | yes |
| /v1/apple-music/track | Track details | 1 | yes | yes |
Reference
/v1/apple-music/album1 creditcacheablebatchableAlbum details
Public Apple Music album: artist, release date, genre, artwork and total runtime. The track listing and copyright line are in raw, behind include_raw=true.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | yes | Apple Music album id (1708308989) or a music.apple.com album URL |
| url | string | no | Apple Music album id (1708308989) or a music.apple.com album URL |
Also accepts cache_max_age (a hit costs 0 credits) and include_raw (returns the untouched upstream payload under raw).
Returns one Post.
Fields
| Field | Type | Present | Description |
|---|---|---|---|
| platform | one of 32 strings | always | |
| id | string | always | |
| type | video | short | image | carousel | text | live | story | reel | audio | album | episode | unknown | always | What kind of post this is. `short` and `reel` are short-form video formats, `live` is a live stream or a recording of one, `carousel` holds several images or videos, `audio` is a music track, `album` is an album, `episode` is a podcast episode, and `unknown` covers anything else. |
| url | string | null | always | |
| title | string | null | always | |
| text | string | null | always | The post's text: a caption, a description or the message body. |
| authorId | string | null | always | |
| authorHandle | string | null | always | |
| authorName | string | null | always | |
| thumbnailUrl | string | null | always | |
| mediaUrls | string[] | always | |
| durationSeconds | number | null | always | |
| viewCount | number | null | always | Views. Null means the platform did not expose a count, which is not the same as zero. |
| likeCount | number | null | always | Likes, or the platform's nearest equivalent. Null means not exposed, not zero. |
| commentCount | number | null | always | Comments. Null means not exposed, not zero. |
| shareCount | number | null | always | Shares, reposts, or the platform's nearest equivalent. Null means not exposed, not zero. |
| hashtags | string[] | always | Hashtags or the platform's own topic tags, without the leading #. |
| mentions | string[] | always | Handles mentioned in the post text, without the leading @. |
| taggedUsers | string[] | may be absent | Usernames tagged in the media itself, as distinct from `mentions`, which come from the caption text. Absent when the platform does not report tags; an empty array means it reports none. |
| isSponsored | boolean | null | always | Whether the platform labels the post as an ad, promoted content or a paid partnership. |
| publishedAt | string | null | always | When the post was published. Usually an ISO 8601 timestamp; some platforms expose only a date or relative text such as "3 days ago". |
| fetchedAt | string | always | When this record was fetched from the platform, as an ISO 8601 timestamp. A cached response keeps the time of the original fetch. |
curl "$API/v1/apple-music/album?id=%3Cid%3E&cache_max_age=7d" \
-H "x-api-key: $KEY"const query = new URLSearchParams({
id: '<id>',
cache_max_age: '7d',
});
const response = await fetch(`${API}/v1/apple-music/album?${query}`, {
headers: { 'x-api-key': KEY },
});
const body = await response.json();
if (!body.success) throw new Error(body.error.code);
// 1 credit, 0 on a cache hit
console.log(body.data, body.meta.creditsCharged);Python
import os, httpx
r = httpx.get(
f"{os.environ['API']}/v1/apple-music/album",
params={
"id": "<id>",
"cache_max_age": "7d",
},
headers={"x-api-key": os.environ["KEY"]},
timeout=30,
)
body = r.json()
if not body["success"]:
raise RuntimeError(body["error"]["code"])
print(body["data"], body["meta"]["creditsCharged"])Response codes
| 200 | Success |
| 400 | invalid_request. Not charged |
| 401 | missing_api_key / invalid_api_key / revoked_api_key. Never a billing error |
| 402 | insufficient_credits. The key is valid, the balance is not |
| 429 | daily_cap_exceeded or upstream_rate_limited. Not charged |
| 501 | not_configured. This deployment is not set up to serve this endpoint. Not charged. |
| 502 | upstream_blocked / upstream_schema_drift. Not charged |
| 504 | upstream_timeout. Not charged |
/v1/apple-music/artist1 creditcacheablebatchableArtist details
Public Apple Music artist page: editorial bio, artist image, banner and genre. Apple publishes no follower or play counts, so those fields are null rather than guessed.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | yes | Apple Music artist id (159260351) or a music.apple.com artist URL |
| url | string | no | Apple Music artist id (159260351) or a music.apple.com artist URL |
Also accepts cache_max_age (a hit costs 0 credits) and include_raw (returns the untouched upstream payload under raw).
Returns one Creator.
Fields
| Field | Type | Present | Description |
|---|---|---|---|
| platform | one of 32 strings | always | |
| id | string | always | The platform's own id for the account, such as a channel id or a numeric user id, always as a string. |
| handle | string | null | always | The account handle without the leading @. Null when the platform has no handles. |
| displayName | string | null | always | |
| bio | string | null | always | |
| avatarUrl | string | null | always | |
| bannerUrl | string | null | always | |
| url | string | null | always | |
| verified | boolean | null | always | Whether the platform shows a verification badge on the account. Null where the platform has no public badge or does not expose it. |
| followerCount | number | null | always | Followers, subscribers, or the platform's nearest equivalent. Null when the account hides the count or the platform does not expose it. |
| followingCount | number | null | always | |
| postCount | number | null | always | Posts, videos, tracks, repositories, or the platform's nearest equivalent that the account has published. |
| viewCount | number | null | always | Lifetime views across the account, where the platform exposes a total. |
| likeCount | number | null | may be absent | Total likes received across all of the creator's posts. Distinct from `viewCount`. Absent when the platform does not expose it. |
| isPrivate | boolean | null | always | |
| isBusiness | boolean | null | always | Whether the platform classifies the account as a business or organisation rather than a person or creator. Null where the platform draws no such line or does not expose it. On Facebook every Page counts, including Pages for public figures. |
| category | string | null | always | A label the platform attaches to the account, such as a business category, a genre or an account type. What it holds varies by platform. |
| location | string | null | always | Location as the platform shows it: a city, a country, a region code or an address, depending on the platform. |
| externalLinks | string[] | always | Links the account lists on its profile, such as a website or other social accounts. |
| createdAt | string | null | always | When the account was created. Usually an ISO 8601 timestamp; some platforms expose only the date as shown on the profile. |
| fetchedAt | string | always | When this record was fetched from the platform, as an ISO 8601 timestamp. A cached response keeps the time of the original fetch. |
curl "$API/v1/apple-music/artist?id=%3Cid%3E&cache_max_age=7d" \
-H "x-api-key: $KEY"const query = new URLSearchParams({
id: '<id>',
cache_max_age: '7d',
});
const response = await fetch(`${API}/v1/apple-music/artist?${query}`, {
headers: { 'x-api-key': KEY },
});
const body = await response.json();
if (!body.success) throw new Error(body.error.code);
// 1 credit, 0 on a cache hit
console.log(body.data, body.meta.creditsCharged);Python
import os, httpx
r = httpx.get(
f"{os.environ['API']}/v1/apple-music/artist",
params={
"id": "<id>",
"cache_max_age": "7d",
},
headers={"x-api-key": os.environ["KEY"]},
timeout=30,
)
body = r.json()
if not body["success"]:
raise RuntimeError(body["error"]["code"])
print(body["data"], body["meta"]["creditsCharged"])Response codes
| 200 | Success |
| 400 | invalid_request. Not charged |
| 401 | missing_api_key / invalid_api_key / revoked_api_key. Never a billing error |
| 402 | insufficient_credits. The key is valid, the balance is not |
| 429 | daily_cap_exceeded or upstream_rate_limited. Not charged |
| 501 | not_configured. This deployment is not set up to serve this endpoint. Not charged. |
| 502 | upstream_blocked / upstream_schema_drift. Not charged |
| 504 | upstream_timeout. Not charged |
/v1/apple-music/search1 creditcacheablebatchableSearch
Search the Apple Music catalogue for tracks, albums or artists. Tracks and albums come back as posts; artists come back as creators.
| Parameter | Type | Required | Description |
|---|---|---|---|
| query | string | yes | Search terms |
| type | track | album | artist | song | no | song is the same as trackdefaults to track |
| limit | number | no | defaults to 25 |
| country | string | no | Two-letter storefront, e.g. us, gb, dedefaults to us |
Also accepts cache_max_age (a hit costs 0 credits) and include_raw (returns the untouched upstream payload under raw).
Returns a list of Post | Creator in data.items.
Fields
| Post | Type | Present | Description |
|---|---|---|---|
| platform | one of 32 strings | always | |
| id | string | always | |
| type | video | short | image | carousel | text | live | story | reel | audio | album | episode | unknown | always | What kind of post this is. `short` and `reel` are short-form video formats, `live` is a live stream or a recording of one, `carousel` holds several images or videos, `audio` is a music track, `album` is an album, `episode` is a podcast episode, and `unknown` covers anything else. |
| url | string | null | always | |
| title | string | null | always | |
| text | string | null | always | The post's text: a caption, a description or the message body. |
| authorId | string | null | always | |
| authorHandle | string | null | always | |
| authorName | string | null | always | |
| thumbnailUrl | string | null | always | |
| mediaUrls | string[] | always | |
| durationSeconds | number | null | always | |
| viewCount | number | null | always | Views. Null means the platform did not expose a count, which is not the same as zero. |
| likeCount | number | null | always | Likes, or the platform's nearest equivalent. Null means not exposed, not zero. |
| commentCount | number | null | always | Comments. Null means not exposed, not zero. |
| shareCount | number | null | always | Shares, reposts, or the platform's nearest equivalent. Null means not exposed, not zero. |
| hashtags | string[] | always | Hashtags or the platform's own topic tags, without the leading #. |
| mentions | string[] | always | Handles mentioned in the post text, without the leading @. |
| taggedUsers | string[] | may be absent | Usernames tagged in the media itself, as distinct from `mentions`, which come from the caption text. Absent when the platform does not report tags; an empty array means it reports none. |
| isSponsored | boolean | null | always | Whether the platform labels the post as an ad, promoted content or a paid partnership. |
| publishedAt | string | null | always | When the post was published. Usually an ISO 8601 timestamp; some platforms expose only a date or relative text such as "3 days ago". |
| fetchedAt | string | always | When this record was fetched from the platform, as an ISO 8601 timestamp. A cached response keeps the time of the original fetch. |
| Creator | Type | Present | Description |
|---|---|---|---|
| platform | one of 32 strings | always | |
| id | string | always | The platform's own id for the account, such as a channel id or a numeric user id, always as a string. |
| handle | string | null | always | The account handle without the leading @. Null when the platform has no handles. |
| displayName | string | null | always | |
| bio | string | null | always | |
| avatarUrl | string | null | always | |
| bannerUrl | string | null | always | |
| url | string | null | always | |
| verified | boolean | null | always | Whether the platform shows a verification badge on the account. Null where the platform has no public badge or does not expose it. |
| followerCount | number | null | always | Followers, subscribers, or the platform's nearest equivalent. Null when the account hides the count or the platform does not expose it. |
| followingCount | number | null | always | |
| postCount | number | null | always | Posts, videos, tracks, repositories, or the platform's nearest equivalent that the account has published. |
| viewCount | number | null | always | Lifetime views across the account, where the platform exposes a total. |
| likeCount | number | null | may be absent | Total likes received across all of the creator's posts. Distinct from `viewCount`. Absent when the platform does not expose it. |
| isPrivate | boolean | null | always | |
| isBusiness | boolean | null | always | Whether the platform classifies the account as a business or organisation rather than a person or creator. Null where the platform draws no such line or does not expose it. On Facebook every Page counts, including Pages for public figures. |
| category | string | null | always | A label the platform attaches to the account, such as a business category, a genre or an account type. What it holds varies by platform. |
| location | string | null | always | Location as the platform shows it: a city, a country, a region code or an address, depending on the platform. |
| externalLinks | string[] | always | Links the account lists on its profile, such as a website or other social accounts. |
| createdAt | string | null | always | When the account was created. Usually an ISO 8601 timestamp; some platforms expose only the date as shown on the profile. |
| fetchedAt | string | always | When this record was fetched from the platform, as an ISO 8601 timestamp. A cached response keeps the time of the original fetch. |
curl "$API/v1/apple-music/search?query=ai%20agents&cache_max_age=7d" \
-H "x-api-key: $KEY"const query = new URLSearchParams({
query: 'ai agents',
cache_max_age: '7d',
});
const response = await fetch(`${API}/v1/apple-music/search?${query}`, {
headers: { 'x-api-key': KEY },
});
const body = await response.json();
if (!body.success) throw new Error(body.error.code);
// 1 credit, 0 on a cache hit
console.log(body.data, body.meta.creditsCharged);Python
import os, httpx
r = httpx.get(
f"{os.environ['API']}/v1/apple-music/search",
params={
"query": "ai agents",
"cache_max_age": "7d",
},
headers={"x-api-key": os.environ["KEY"]},
timeout=30,
)
body = r.json()
if not body["success"]:
raise RuntimeError(body["error"]["code"])
print(body["data"], body["meta"]["creditsCharged"])Response codes
| 200 | Success |
| 400 | invalid_request. Not charged |
| 401 | missing_api_key / invalid_api_key / revoked_api_key. Never a billing error |
| 402 | insufficient_credits. The key is valid, the balance is not |
| 429 | daily_cap_exceeded or upstream_rate_limited. Not charged |
| 501 | not_configured. This deployment is not set up to serve this endpoint. Not charged. |
| 502 | upstream_blocked / upstream_schema_drift. Not charged |
| 504 | upstream_timeout. Not charged |
/v1/apple-music/track1 creditcacheablebatchableTrack details
Public Apple Music track: artist, album, duration, genre, release date and the 30-second preview URL.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | yes | Apple Music track id (1833328840), a /song/ URL, or an album URL with ?i=<track id> |
| url | string | no | Apple Music track id (1833328840), a /song/ URL, or an album URL with ?i=<track id> |
Also accepts cache_max_age (a hit costs 0 credits) and include_raw (returns the untouched upstream payload under raw).
Returns one Post.
Fields
| Field | Type | Present | Description |
|---|---|---|---|
| platform | one of 32 strings | always | |
| id | string | always | |
| type | video | short | image | carousel | text | live | story | reel | audio | album | episode | unknown | always | What kind of post this is. `short` and `reel` are short-form video formats, `live` is a live stream or a recording of one, `carousel` holds several images or videos, `audio` is a music track, `album` is an album, `episode` is a podcast episode, and `unknown` covers anything else. |
| url | string | null | always | |
| title | string | null | always | |
| text | string | null | always | The post's text: a caption, a description or the message body. |
| authorId | string | null | always | |
| authorHandle | string | null | always | |
| authorName | string | null | always | |
| thumbnailUrl | string | null | always | |
| mediaUrls | string[] | always | |
| durationSeconds | number | null | always | |
| viewCount | number | null | always | Views. Null means the platform did not expose a count, which is not the same as zero. |
| likeCount | number | null | always | Likes, or the platform's nearest equivalent. Null means not exposed, not zero. |
| commentCount | number | null | always | Comments. Null means not exposed, not zero. |
| shareCount | number | null | always | Shares, reposts, or the platform's nearest equivalent. Null means not exposed, not zero. |
| hashtags | string[] | always | Hashtags or the platform's own topic tags, without the leading #. |
| mentions | string[] | always | Handles mentioned in the post text, without the leading @. |
| taggedUsers | string[] | may be absent | Usernames tagged in the media itself, as distinct from `mentions`, which come from the caption text. Absent when the platform does not report tags; an empty array means it reports none. |
| isSponsored | boolean | null | always | Whether the platform labels the post as an ad, promoted content or a paid partnership. |
| publishedAt | string | null | always | When the post was published. Usually an ISO 8601 timestamp; some platforms expose only a date or relative text such as "3 days ago". |
| fetchedAt | string | always | When this record was fetched from the platform, as an ISO 8601 timestamp. A cached response keeps the time of the original fetch. |
curl "$API/v1/apple-music/track?id=%3Cid%3E&cache_max_age=7d" \
-H "x-api-key: $KEY"const query = new URLSearchParams({
id: '<id>',
cache_max_age: '7d',
});
const response = await fetch(`${API}/v1/apple-music/track?${query}`, {
headers: { 'x-api-key': KEY },
});
const body = await response.json();
if (!body.success) throw new Error(body.error.code);
// 1 credit, 0 on a cache hit
console.log(body.data, body.meta.creditsCharged);Python
import os, httpx
r = httpx.get(
f"{os.environ['API']}/v1/apple-music/track",
params={
"id": "<id>",
"cache_max_age": "7d",
},
headers={"x-api-key": os.environ["KEY"]},
timeout=30,
)
body = r.json()
if not body["success"]:
raise RuntimeError(body["error"]["code"])
print(body["data"], body["meta"]["creditsCharged"])Response codes
| 200 | Success |
| 400 | invalid_request. Not charged |
| 401 | missing_api_key / invalid_api_key / revoked_api_key. Never a billing error |
| 402 | insufficient_credits. The key is valid, the balance is not |
| 429 | daily_cap_exceeded or upstream_rate_limited. Not charged |
| 501 | not_configured. This deployment is not set up to serve this endpoint. Not charged. |
| 502 | upstream_blocked / upstream_schema_drift. Not charged |
| 504 | upstream_timeout. Not charged |
Questions
- How much does the Apple Music API cost?
- Calls cost 1 credit each. Every endpoint is cacheable, and a cache hit costs nothing. A failed request and an empty result are both free, on every endpoint.
- Does the Apple Music API need a login or cookies?
- No. Every endpoint reads public, logged-out pages only — no account, no cookies, no session. An API key identifies your own TrueScrape account and nothing else.
- Can I fetch many Apple Music targets in one request?
- Yes. 4 of 4 are batchable: one POST to /v1/jobs/batch takes many targets and returns a job id to poll.
- Can I watch Apple Music endpoints for changes?
- Yes, 3 of 4. A subscription polls on your schedule and fires your webhook only when the content changed; the rest carry x-subscribable: false in the spec.