Developers

OnRepeat Stats API

Listening statistics for the signed-in owner, and shared public profiles under /v1/public/{handle}. /v1 changes are additive; removals and changes of meaning go to /v2.

Version 1.7.0 · base URL https://api.onrepeatstats.com · OpenAPI document · generated from it, so this page always matches the API.

Authentication

Owner operations read the signed-in account’s own data. Apps sign in with OAuth 2.1: OAuth 2.1 with PKCE (S256). Ask for resource https://api.onrepeatstats.com/v1 to get a 15-minute JWT access token; offline_access adds a 60-day refresh token that rotates on use. Claude and ChatGPT identify themselves with a client metadata document (an HTTPS client_id on their own host); other clients register dynamically (RFC 7591) or are registered by OnRepeat. Metadata: https://onrepeatstats.com/.well-known/oauth-authorization-server.

  • history:read: Read the listening history and stats, account and import status
  • offline_access: A refresh token, to stay connected for 60 days

Send the access token as Authorization: Bearer …. Operations marked “Session only” are for the website and app. Public operations need nothing and answer only for shared profiles. AI assistants use the MCP server instead.

Operations

Stats

GET /v1/stats/summary

A range at a glance: totals, top artists, albums and songs, trend and hours getPeriodSummary

Totals and active days, the top artists, albums and songs, the trend (days, months or years by the length of the listening), the peak bucket, and listening by local hour (estimated from a sample on long ranges).

Ownerscope history:readOAuth or sessionMCP tool

Parameters

NameTypeDescription
fromdateFirst local day, inclusive. Absent: from the beginning.
todateLast local day, inclusive. Absent: through today.
by"minutes" | "plays"default "minutes"
combineSongVersionsbooleanGroup a song's versions (live, remixes) together. Default: the account setting.
combineAlbumEditionsbooleanGroup an album's editions together. Default: the account setting.
provider"apple" | "spotify"Count one provider only: apple or spotify. Default: both.
limitintegerItems in each top list. · default 5 · 1–10

Returns StatsSummary · other statuses 400, 401

GET /v1/stats/compare

Two ranges side by side: totals, and how the top entities moved compareRanges

Each range’s totals, and the top `limit` artists, albums or songs of each, with their position and numbers in both and whether they are new, gone, up, down or the same from a to b.

Ownerscope history:readOAuth or sessionMCP tool

Parameters

NameTypeDescription
kind required"artist" | "album" | "song"
aFromdateFirst range, first local day. Absent: from the beginning.
aTodateFirst range, last local day. Absent: through today.
bFromdateSecond range, first local day. Absent: from the beginning.
bTodateSecond range, last local day. Absent: through today.
by"minutes" | "plays"default "minutes"
combineSongVersionsbooleanGroup a song's versions (live, remixes) together. Default: the account setting.
combineAlbumEditionsbooleanGroup an album's editions together. Default: the account setting.
provider"apple" | "spotify"Count one provider only: apple or spotify. Default: both.
limitintegerThe top of each range to compare. · default 10 · 1–25

Returns StatsComparison · other statuses 400, 401

GET /v1/stats/discoveries

Artists, albums or songs first heard in a range listDiscoveries

Entities whose first listening ever falls within the range, ranked by their listening within it, each with the day it was first heard.

Ownerscope history:readOAuth or sessionMCP tool

Parameters

NameTypeDescription
kind required"artist" | "album" | "song"
fromdateFirst local day, inclusive. Absent: from the beginning.
todateLast local day, inclusive. Absent: through today.
by"minutes" | "plays"default "minutes"
combineSongVersionsbooleanGroup a song's versions (live, remixes) together. Default: the account setting.
combineAlbumEditionsbooleanGroup an album's editions together. Default: the account setting.
provider"apple" | "spotify"Count one provider only: apple or spotify. Default: both.
limitintegerdefault 20 · 1–50
cursorstringnextCursor from the previous page.

Returns StatsDiscoveries · other statuses 400, 401

GET /v1/stats/rankings

Top artists, albums or songs getRankings

A chart for a range of local days, by listening time or plays, with stable entity IDs.

Ownerscope history:readOAuth or sessionMCP tool

Parameters

NameTypeDescription
kind required"artist" | "album" | "song"
fromdateFirst local day, inclusive. Absent: from the beginning.
todateLast local day, inclusive. Absent: through today.
by"minutes" | "plays"default "minutes"
combineSongVersionsbooleanGroup a song's versions (live, remixes) together. Default: the account setting.
combineAlbumEditionsbooleanGroup an album's editions together. Default: the account setting.
withinstringOnly count listening within this entity (an artist, album, song or recording ID).
provider"apple" | "spotify"Count one provider only: apple or spotify. Default: both.
limitintegerdefault 50 · 1–100
cursorstringnextCursor from the previous page.

Returns StatsRankings · other statuses 400, 401, 404

GET /v1/stats/entities/{id}

One entity: totals, first and last plays, trend and top children getEntityStats

Ownerscope history:readOAuth or sessionMCP tool

Parameters

NameTypeDescription
id required pathstring
fromdateFirst local day, inclusive. Absent: from the beginning.
todateLast local day, inclusive. Absent: through today.
by"minutes" | "plays"default "minutes"
combineSongVersionsbooleanGroup a song's versions (live, remixes) together. Default: the account setting.
combineAlbumEditionsbooleanGroup an album's editions together. Default: the account setting.
bucket"day" | "week" | "month" | "year"default "month"
weekStartintegerFirst day of week buckets: 0 Sunday … 6 Saturday. Default: the account's setting, else Sunday. · 0–6
childrenintegerTop entities of each other kind to include. · default 10 · 1–50

Returns StatsEntity · other statuses 400, 401, 404

GET /v1/stats/trend

Listening by day, week, month or year getTrend

Ownerscope history:readOAuth or sessionMCP tool

Parameters

NameTypeDescription
fromdateFirst local day, inclusive. Absent: from the beginning.
todateLast local day, inclusive. Absent: through today.
by"minutes" | "plays"default "minutes"
combineSongVersionsbooleanGroup a song's versions (live, remixes) together. Default: the account setting.
combineAlbumEditionsbooleanGroup an album's editions together. Default: the account setting.
bucket"day" | "week" | "month" | "year"default "month"
weekStartintegerFirst day of week buckets: 0 Sunday … 6 Saturday. Default: the account's setting, else Sunday. · 0–6
withinstringOnly count listening within this entity (an artist, album, song or recording ID).
provider"apple" | "spotify"Count one provider only: apple or spotify. Default: both.
cursorstringnextCursor from the previous page.

Returns StatsTrend · other statuses 400, 401, 404

GET /v1/stats/history

Listening history, newest first listHistory

Source rows with their time window and precision, cursor-paged.

Ownerscope history:readOAuth or sessionMCP tool

Parameters

NameTypeDescription
fromdateFirst local day, inclusive. Absent: from the beginning.
todateLast local day, inclusive. Absent: through today.
by"minutes" | "plays"default "minutes"
combineSongVersionsbooleanGroup a song's versions (live, remixes) together. Default: the account setting.
combineAlbumEditionsbooleanGroup an album's editions together. Default: the account setting.
withinstringOnly count listening within this entity (an artist, album, song or recording ID).
provider"apple" | "spotify"Count one provider only: apple or spotify. Default: both.
cursorstringnextCursor from the previous page.
limitintegerdefault 50 · 1–100
include"music" | "all"music: the app's list (music rows with a play or listening time). all: every source row. · default "music"

Returns StatsHistory · other statuses 400, 401, 404

GET /v1/stats/recent

The latest plays listRecent

The first page of history without a range.

Ownerscope history:readOAuth or sessionMCP tool

Parameters

NameTypeDescription
by"minutes" | "plays"default "minutes"
combineSongVersionsbooleanGroup a song's versions (live, remixes) together. Default: the account setting.
combineAlbumEditionsbooleanGroup an album's editions together. Default: the account setting.
provider"apple" | "spotify"Count one provider only: apple or spotify. Default: both.
cursorstringnextCursor from the previous page.
limitintegerdefault 20 · 1–50
include"music" | "all"music: the app's list (music rows with a play or listening time). all: every source row. · default "music"

Returns StatsHistory · other statuses 400, 401

GET /v1/stats/resolve

Entities for an ID, an external ID or a title and artist resolve

Shared tracks: a trk_ ID resolves to the recordings of its track family; an external ID also finds recordings that joined its track through another ID.

Ownerscope history:readOAuth or sessionMCP tool

Parameters

NameTypeDescription
by"minutes" | "plays"default "minutes"
combineSongVersionsbooleanGroup a song's versions (live, remixes) together. Default: the account setting.
combineAlbumEditionsbooleanGroup an album's editions together. Default: the account setting.
idstringAn entity ID, or a shared track ID (trk_…).
schemestringExternal ID scheme: apple-catalog, apple-library, isrc, spotify or mbid.
valuestringThe external ID (with scheme).
titlestringSong title (with artist).
artiststringArtist name (with title).
albumstringAlbum title (optional, with title and artist).

Returns StatsResolved · other statuses 400, 401

GET /v1/stats/exclusions

Artists, albums and songs left out of stats listExclusions

Each rule with what it names now and its metadata key, so a client can skip those recordings.

Ownerscope history:readOAuth or session

Returns StatsExclusions · other statuses 400, 401

POST /v1/stats/exclusions

Leave an artist, album or song out of stats addExclusion

By entity ID, or by kind and the metadata key the app has (a credit or grouping key). Its plays are kept. Adding a rule that exists changes nothing.

Ownerscope settings:writeSession only

JSON body

NameTypeDescription
idstringAn artist, album or song ID (art_, alb_ or song_).
kindStatsRankingKind
keystringWith kind: the metadata key. · up to 1000 characters

Returns StatsExclusions · other statuses 400, 401, 402, 404, 409, 413

DELETE /v1/stats/exclusions/{id}

Count an artist, album or song in stats again removeExclusion

By the ID a rule was saved with or the ID its entity has now. Removing a rule that does not exist changes nothing.

Ownerscope settings:writeSession only

Parameters

NameTypeDescription
id required pathstring

Returns StatsExclusions · other statuses 400, 401, 404

History

GET /v1/history/rules

Changes to the history: removals, merges and splits listHistoryRules

Newest first, a page at a time: every kind, or one kind, optionally only those whose label has some words (q). Counts are by kind, of everything. Each can be undone.

Ownerscope history:readOAuth or session

Parameters

NameTypeDescription
limitintegerdefault 100 · 1–200
beforestringThe next value of the previous page. · up to 64 characters
kindHistoryRuleKindOnly rules of this kind.
qstringOnly rules whose label has these words: a song or artist, a period, a name. · up to 100 characters

Returns HistoryRules · other statuses 400, 401

POST /v1/history/rules

Remove plays, or merge or keep apart artists, albums and songs addHistoryRule

A play by its history row (seq), or a period by its first instant and the instant after it, as the person chose it in their time zone, with a label such as “March 2024”. A play is in a period when the middle of its time window is. Removed plays stay removed when imported again. An artist, album or song removes its plays past and future; a merge joins one into another; a split keeps a version or edition apart.

Ownerscope history:writeSession only

JSON body

NameTypeDescription
kind required"play" | "period" | "entity" | "merge" | "split"
seqintegerA play: its history row. · at most 9007199254740991
startAtdate-timeA period: its first instant. · pattern ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
endAtdate-timeA period: the instant after it. · pattern ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
labelstringA period: as it was chosen. · up to 100 characters
idstringAn artist, album or song: its ID (art_, alb_ or song_).
entityKindStatsRankingKindOr, with key: its kind.
keystringWith entityKind: the metadata key. · up to 1000 characters
intostringA merge: the artist, album or song it joins (its ID).
intoKeystringOr, with entityKind: the metadata key of the one it joins. · up to 1000 characters

Returns HistoryRule · other statuses 400, 401, 402, 404, 409

DELETE /v1/history/rules/{id}

Undo a change to the history undoHistoryRule

Undoes a removal (the plays only it removed come back), a merge or a split.

Ownerscope history:writeSession only

Parameters

NameTypeDescription
id required pathstringup to 64 characters

Returns object · other statuses 400, 401, 404

Imports and setup

GET /v1/export-guides/{provider}

How to get a listening history export from Apple Music, Spotify or Last.fm getExportGuide

Ownerscope history:readOAuth or sessionMCP tool

Parameters

NameTypeDescription
provider required pathProvider

Returns ExportGuide · other statuses 400, 401, 404

GET /v1/imports

The account’s imports, newest first, and its import limits listImports

Ownerscope history:readOAuth or sessionMCP tool

Parameters

NameTypeDescription
limitintegerAt most this many imports (1–100, default 100). · default 100 · 1–100

Returns ImportList · other statuses 400, 401

GET /v1/imports/{id}

One import getImport

Ownerscope history:readOAuth or sessionMCP tool

Parameters

NameTypeDescription
id required pathstringpattern ^[\w-]{1,100}$

Returns ImportDetail · other statuses 400, 401, 404

GET /v1/apple-music/connection

Apple Music detection: availability, connection, and its checks getAppleMusicStatus

No tokens. Connecting and stopping are on the website and in the app.

Ownerscope history:readOAuth or sessionMCP tool

Returns AppleMusicStatus · other statuses 400, 401

GET /v1/handoffs/check

Whether a link from an assistant was made for the signed-in account checkHandoff

The website’s Imports and Live tracking pages ask before acting on a link with `for`: valid, expired, or made for another account (the page then offers nothing).

Ownerscope accountSession only

Parameters

NameTypeDescription
purpose required"upload" | "apple-music"
for requiredstringup to 200 characters

Returns HandoffCheck · other statuses 400, 401

Account

GET /v1/settings

The account's settings getSettings

Ownerscope history:readOAuth or session

Returns AccountSettings · other statuses 400, 401

PATCH /v1/settings

Change the account's settings updateSettings

Fields absent from the body keep their value; a set value is never unset.

Ownerscope settings:writeSession only

JSON body

NameTypeDescription
timeZonestringIANA time zone, such as America/Chicago.
combineSongVersionsboolean
combineAlbumEditionsboolean
weekStartintegerFirst day of week buckets: 0 Sunday … 6 Saturday. · 0–6

Returns AccountSettings · other statuses 400, 401, 413

GET /v1/account/status

Where the account stands: history, imports, Apple Music and next steps getAccountStatus

Ownerscope history:readOAuth or sessionMCP tool

Returns AccountStatus · other statuses 400, 401

POST /v1/settings/defaults

Set the time zone and week start where they are still unset fillSettings

For the website: sent from the browser after each sign-in (so a new account has them before its first import) and from the Imports and Live tracking pages. A value already set (by the app or earlier) is never changed.

Ownerscope accountSession only

JSON body

NameTypeDescription
timeZonestring
weekStartinteger0–6

Returns AccountSettings · other statuses 400, 401, 413

GET /v1/account/connections

Apps connected to the account listConnections

Ownerscope accountSession only

Returns Connections · other statuses 400, 401

POST /v1/account/connections/revoke

Disconnect an app disconnectApp

Removes the consent and tokens; tokens the app already holds stop working at once. The app must ask again to reconnect.

Ownerscope accountSession only

JSON body

NameTypeDescription
clientId requiredstringup to 500 characters

Returns object · other statuses 400, 401, 413

GET /v1/billing

The account's subscription getBilling

Ownerscope history:readOAuth or session

Returns BillingState · other statuses 400, 401

POST /v1/billing/offer

Which subscription the app offers getBillingOffer

The product the paywall shows: with the 1-month trial, or without it when this account, its email or this device (DeviceCheck) already had one. Also the appAccountToken to purchase with.

Ownerscope accountSession only

JSON body

NameTypeDescription
deviceTokenstringDCDevice token, base64. · up to 8192 characters
developmentbooleanA development build (DeviceCheck sandbox).

Returns object · other statuses 400, 401

POST /v1/billing/apple/transactions

Send a purchase from the app recordAppleTransaction

A signed App Store transaction (and optionally its renewal information) the app saw: a purchase, a restore, or an update. Verified with Apple’s certificates before anything is stored. 409 when the subscription belongs to another account.

Ownerscope accountSession only

JSON body

NameTypeDescription
signedTransaction requiredstringup to 32768 characters
signedRenewalstringup to 32768 characters
deviceTokenstringup to 8192 characters
developmentboolean

Returns BillingState · other statuses 400, 401, 409

Public profiles

GET /v1/public/{handle}/profile

A public profile: totals and the top 5 artists, albums and songs of a period getProfilePublic

The owner previews their own profile whether or not it is shared. Public viewers read it at /v1/public/{handle}/profile once shared; private and missing profiles are the same 404. The account’s hidden entities leave the lists.

Public

Parameters

NameTypeDescription
handle required pathstringThe profile handle, any capitals. · pattern ^[A-Za-z0-9_]{1,40}$
periodstringweeks: the past 4 weeks. year: this calendar year (the default). Last year by its number (2025). all: All time. Last year and All time need the profile to show its history.

Returns PublicProfile · other statuses 301, 400, 404, 429

GET /v1/public/{handle}/profile/details

A public profile’s slower sections: every day, records, hours, weekdays, new artists getProfileDetailsPublic

Public

Parameters

NameTypeDescription
handle required pathstringThe profile handle, any capitals. · pattern ^[A-Za-z0-9_]{1,40}$
periodstringweeks: the past 4 weeks. year: this calendar year (the default). Last year by its number (2025). all: All time. Last year and All time need the profile to show its history.

Returns ProfileDetails · other statuses 301, 400, 404, 429

GET /v1/public/{handle}/profile/top

A public profile’s top 25 artists, albums or songs of a period, and how its top 5 moved getProfileTopPublic

Public

Parameters

NameTypeDescription
handle required pathstringThe profile handle, any capitals. · pattern ^[A-Za-z0-9_]{1,40}$
periodstringweeks: the past 4 weeks. year: this calendar year (the default). Last year by its number (2025). all: All time. Last year and All time need the profile to show its history.
kind"artist" | "album" | "song"default "artist"

Returns ProfileTop · other statuses 301, 400, 404, 429

GET /v1/public/{handle}/profile/recent

A public profile’s recently played songs (when it shows them) getProfileRecentPublic

Public

Parameters

NameTypeDescription
handle required pathstringThe profile handle, any capitals. · pattern ^[A-Za-z0-9_]{1,40}$

Returns ProfileRecent · other statuses 301, 400, 404, 429

Profile settings

GET /v1/profile

The account's public profile settings getProfileSettings

Ownerscope profile:readSession only

Returns ProfileSettings · other statuses 400, 401

PATCH /v1/profile

Change the account's public profile updateProfileSettings

Fields absent from the body keep their value. Sharing needs a handle; a new handle keeps the old one as a redirect.

Ownerscope settings:writeSession only

JSON body

NameTypeDescription
handlestring
sharedboolean
displayNamestring
showRecentboolean
showHistoryboolean

Returns ProfileSettings · other statuses 400, 401, 402, 409, 413

GET /v1/profile/handle

Whether a handle is available checkProfileHandle

Ownerscope profile:readSession only

Parameters

NameTypeDescription
handle requiredstringThe handle to check. · up to 40 characters

Returns HandleCheck · other statuses 400, 401

GET /v1/profile/view

A public profile: totals and the top 5 artists, albums and songs of a period getProfile

The owner previews their own profile whether or not it is shared. Public viewers read it at /v1/public/{handle}/profile once shared; private and missing profiles are the same 404. The account’s hidden entities leave the lists.

Public

Parameters

NameTypeDescription
periodstringweeks: the past 4 weeks. year: this calendar year (the default). Last year by its number (2025). all: All time. Last year and All time need the profile to show its history.

Returns PublicProfile · other statuses 400, 401, 404

GET /v1/profile/details

A public profile’s slower sections: every day, records, hours, weekdays, new artists getProfileDetails

Public

Parameters

NameTypeDescription
periodstringweeks: the past 4 weeks. year: this calendar year (the default). Last year by its number (2025). all: All time. Last year and All time need the profile to show its history.

Returns ProfileDetails · other statuses 400, 401, 404

GET /v1/profile/top

A public profile’s top 25 artists, albums or songs of a period, and how its top 5 moved getProfileTop

Public

Parameters

NameTypeDescription
periodstringweeks: the past 4 weeks. year: this calendar year (the default). Last year by its number (2025). all: All time. Last year and All time need the profile to show its history.
kind"artist" | "album" | "song"default "artist"

Returns ProfileTop · other statuses 400, 401, 404

GET /v1/profile/recent

A public profile’s recently played songs (when it shows them) getProfileRecent

Public

Returns ProfileRecent · other statuses 400, 401, 404

Data export

POST /v1/exports

Start a download of all the account’s data startExport

Writes one ZIP in the background: README.txt, account.json, imports.json, plays.csv and plays.jsonl. One at a time, and at most limits.perDay per 24 hours. 409 while one is being written; 429 over the daily limit (retryAt says when); 403 with reason mfa for a session that needs to pass two-step sign-in.

Ownerscope accountSession only

Returns DataExportStatus · other statuses 400, 401, 403, 409, 429, 503

GET /v1/exports/status

The account’s data download: the latest one and whether another can start exportStatus

Ownerscope accountSession only

Returns DataExportStatus · other statuses 400, 401

GET /v1/exports/{id}/download

Download a finished data export (a ZIP) downloadExport

While the export is ready. 409 before it is; 410 once its week is up and the file is deleted.

Ownerscope accountSession only

Parameters

NameTypeDescription
id required pathuuidpattern ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$

Returns a file (application/zip) · other statuses 400, 401, 403, 404, 409, 410

Schemas

AccountSettings

Account settings. null: never set; clients then offer their own value or use the default.

NameTypeDescription
timeZone requiredstring | nullIANA time zone; null until set.
combineSongVersions requiredboolean | null
combineAlbumEditions requiredboolean | null
weekStart requiredinteger | nullFirst day of week buckets: 0 Sunday … 6 Saturday. null: Sunday.
hiddenEntities requiredstring[]IDs of the artists, albums and songs left out of stats (/v1/stats/exclusions).
updatedAt requiredstring | null

AccountStatus

Where the account stands: history, imports, Apple Music, and what to do next.

NameTypeDescription
account requiredobject
history requiredobject
imports requiredobject
usage requiredImportUsage
appleMusic requiredobject
statsReady requiredbooleanFalse while rows are still being placed in the account’s time zone.
nextSteps requiredNextStep[]
nextActions requiredNextAction[]nextSteps in the same order, each with the tool to call for it.

AppleMusicStatus

Apple Music detection: whether it can connect, whether it is connected, and its last and next checks. Detection sees new plays from when it connects, not past history; times are approximate, within the check window.

NameTypeDescription
available requiredbooleanWhether this account can connect (or is connected).
unavailableReason requiredAppleMusicUnavailable | null
status required"active" | "reauthorize" | "disconnected"
connected requiredboolean
needsReconnect requiredbooleanApple stopped accepting the connection: the user connects again.
stopped requiredbooleanThe user paused detection themselves.
held requiredbooleanPaused while the account has no subscription; it resumes when it has one.
connectedAt requiredstring | null
lastPolledAt requiredstring | nullThe last check of Apple’s recently played list.
nextPollAt requiredstring | null
lastResult requiredstring | null
lastError requiredstring | null
pollingIntervalSeconds requiredinteger-9007199254740991–9007199254740991
playsDetected requiredintegerPlays detection has found so far. · -9007199254740991–9007199254740991
lastPlayDetectedAt requiredstring | nullThe check that found the newest detected play.

AppleMusicUnavailable

Why Apple Music detection can’t be connected: not set up on this server, or at its connection limit (full).

"unconfigured" | "full"

BillingState

NameTypeDescription
enforced requiredbooleanWhether a missing subscription refuses anything yet.
access requiredbooleanWhether the account may use everything now.
state required"none" | "trial" | "active" | "grace" | "granted" | "lapsed"
subscription requiredobject | null
grant requiredobject | nullAccess from OnRepeat (no charge).
endedAt requiredstring | nullWhen access ended, once it has.
keepUntil requiredstring | nullUntil when the history is kept after access ended.

Connection

NameTypeDescription
clientId requiredstring
name requiredstringThe name the app gave itself (or its registration).
verified requiredbooleanWhether OnRepeat confirmed who the app is: an allowed client metadata document, or registered by OnRepeat.
scopes requiredstring[]
connectedAt requiredstring | null
lastUsedAt requiredstring | nullWhen it last got a refresh token.

Connections

Apps connected to the account.

NameTypeDescription
connections requiredConnection[]

DataExport

NameTypeDescription
id requiredstring
status requiredDataExportState
createdAt requiredstring
readyAt requiredstring | null
expiresAt requiredstring | nullWhen the file is deleted (ready exports).
bytes requiredinteger | nullSize of the ZIP once ready.
plays requiredinteger | nullRows in plays.csv, once the export has begun.
progress requirednumber | nullShare written so far, while running.
downloadPath requiredstring | nullThe download operation’s path, while ready.
error requiredstring | null

DataExportState

queued and running: being written; ready: can be downloaded until expiresAt; failed: nothing was kept, start again; expired: the file was deleted.

"queued" | "running" | "ready" | "failed" | "expired"

DataExportStatus

The account’s data export: the latest one and whether another can start.

NameTypeDescription
latest requiredDataExport | nullThe most recent export, null when there is none.
canStart requiredbooleanWhether startExport would be accepted now.
blockedBy required"running" | "daily" | "ceiling" | "off" | nullWhy not: one is being written, the daily limit, the limit for all accounts, or exports are turned off.
nextStartAt requiredstring | nullWhen the daily limit allows another.
limits requiredobject

DurationSource

Where durationMs comes from: reported by the source; split (an even share of an Apple day's time); length (the song's length); gap (the time to the next scrobble); gap+length (that, capped by the song's length), or unknown (0 until a length is known).

"reported" | "split" | "length" | "gap" | "gap+length" | "unknown"

Error

NameTypeDescription
error requiredstring

ExclusionGrouping

"track" | "song" | "album" | "albumGroup"

ExportGuide

How to request a listening history export and what to upload.

NameTypeDescription
provider requiredProvider
service requiredstringApple Music or Spotify.
requestUrl requiredstringWhere the user requests the export, signed in to the service.
steps requiredstring[]What the user does, in order.
typicalWait requiredstring
files requiredobject[]The files OnRepeat reads from the export.
upload requiredstringWhat to upload, and how (getUploadLink gives the link).
guideUrl requiredstringThe full guide on onrepeatstats.com.

HandleCheck

NameTypeDescription
handle requiredstring
available requiredboolean
reason required"format" | "reserved" | "taken" | null

HandoffCheck

Whether a link from an assistant was made for the signed-in account.

NameTypeDescription
result required"valid" | "expired" | "other_account"

HistoryRule

A change the owner made to their history: plays removed, or a merge or split.

NameTypeDescription
id requiredstring
kind requiredHistoryRuleKind
entityId requiredstring | nullAn artist, album or song: its ID (art_, alb_ or song_). A merge: the one merged.
entityKind requiredStatsRankingKind | nullA removed, merged or kept-apart artist, album or song: which it is.
label requiredstringA play: its song and artist. A period: as it was chosen, such as “March 2024”. An artist, album or song: its title (and artist). A merge: “from → into”; show from and into instead. A split: the version or edition’s title.
from requiredstring | nullA merge: the title of the one merged.
into requiredstring | nullA merge: the title of the one it joined (null for a merge whose label has none).
createdAt requiredstring
applying requiredbooleanStill being applied: a large removal takes a few seconds to finish.
earliestAt requiredstring | nullA play: the start of its time window.
latestAt requiredstring | nullA play: the end of its time window.
startAt requiredstring | nullA period: its first instant.
endAt requiredstring | nullA period: the instant after it.

HistoryRuleKind

One play, every play in a period, or every play of an artist, album or song (removed); or a merge of two artists, albums or songs, or a version or edition kept apart from its group.

"play" | "period" | "entity" | "merge" | "split"

HistoryRules

Changes to the history, newest first.

NameTypeDescription
items requiredHistoryRule[]Newest first.
next requiredstring | nullPass as before for the next page.
total requiredintegerRules of every kind. · -9007199254740991–9007199254740991
counts requiredobjectRules of each kind.
limit requiredintegerMost rules an account can keep. · -9007199254740991–9007199254740991

Import

One import of an export file.

NameTypeDescription
id requiredstring
filename requiredstring
status requiredImportState
digest requiredstring | nullSHA-256 of the uploaded file.
objectKey requiredstring | null
checkpoint requiredintegerRows read so far. · -9007199254740991–9007199254740991
accepted requiredintegerRows read and kept as listening. · -9007199254740991–9007199254740991
rejected requiredintegerRows that could not be read. · -9007199254740991–9007199254740991
added requiredintegerNew listening added to the history. · -9007199254740991–9007199254740991
alreadyImported requiredintegerRows already in the history. · -9007199254740991–9007199254740991
replaced requiredintegerExisting rows replaced by a more complete Apple day. · -9007199254740991–9007199254740991
skipped requiredintegerPodcast, audiobook and radio rows, not kept. · -9007199254740991–9007199254740991
error requiredstring | null
retryable requiredboolean
duplicateOf requiredstring | null
createdAt requiredstring
groupId requiredstringThe upload it was part of. Deleting an import deletes its whole upload.
bytes requiredintegerBytes this import keeps in storage. · -9007199254740991–9007199254740991

ImportDetail

One import, and whether stats include everything imported so far.

NameTypeDescription
id requiredstring
filename requiredstring
status requiredImportState
digest requiredstring | nullSHA-256 of the uploaded file.
objectKey requiredstring | null
checkpoint requiredintegerRows read so far. · -9007199254740991–9007199254740991
accepted requiredintegerRows read and kept as listening. · -9007199254740991–9007199254740991
rejected requiredintegerRows that could not be read. · -9007199254740991–9007199254740991
added requiredintegerNew listening added to the history. · -9007199254740991–9007199254740991
alreadyImported requiredintegerRows already in the history. · -9007199254740991–9007199254740991
replaced requiredintegerExisting rows replaced by a more complete Apple day. · -9007199254740991–9007199254740991
skipped requiredintegerPodcast, audiobook and radio rows, not kept. · -9007199254740991–9007199254740991
error requiredstring | null
retryable requiredboolean
duplicateOf requiredstring | null
createdAt requiredstring
groupId requiredstringThe upload it was part of. Deleting an import deletes its whole upload.
bytes requiredintegerBytes this import keeps in storage. · -9007199254740991–9007199254740991
statsReady requiredbooleanFalse while rows are still being placed in the account’s time zone.

ImportList

The account’s imports, newest first, and its import limits.

NameTypeDescription
imports requiredImport[]Newest first, at most 100.
usage requiredImportUsage
statsReady requiredbooleanFalse while rows are still being placed in the account’s time zone.

ImportState

awaiting_upload: created, its file not uploaded yet; queued and processing: being read; complete; failed (error says why); duplicate: the same file as an earlier import (duplicateOf); deleting: being deleted with the rest of its upload.

"awaiting_upload" | "queued" | "processing" | "complete" | "failed" | "duplicate" | "deleting"

ImportUsage

The account’s import limits: uploads in the last 24 hours, storage, imports waiting for their file, and rows in the history.

NameTypeDescription
uploads requiredobject
storage requiredobject
waiting requiredobject
rows requiredobjectRows in the history (imported and detected listening) and the most it can hold. An import that would pass the limit fails whole.

MediaKind

"music" | "podcast" | "audiobook" | "radio" | "unknown"

NextAction

A next step and the tool that carries it out.

NameTypeDescription
step requiredNextStep
tool requiredstringThe MCP tool (and API operation) for this step.

NextStep

"request_export" | "upload_export" | "wait_for_import" | "connect_apple_music" | "reconnect_apple_music" | "ask_questions"

PlayReported

What the play's source itself reported as its time: end (Spotify), start (a scrobble), window (two checks a detected play fell between), hour or day (Apple's daily file).

"end" | "start" | "window" | "hour" | "day"

ProfileDetails

NameTypeDescription
asOf requiredStatsAsOf
handle requiredstring
displayName requiredstring
settingsVersion requiredintegerChanges with every profile settings change. · -9007199254740991–9007199254740991
showRecent requiredboolean
showHistory requiredboolean
period requiredProfilePeriodInfo
periods requiredProfilePeriodInfo[]The periods this profile offers, in order.
today requiredstringToday in the account’s time zone.
timeZone requiredstring
since requiredstring | nullFirst local day with listening.
isNew requiredboolean
artworkPending requiredbooleanSome artwork is still being looked up: read again shortly for it.
calendar requiredobject[]Listening per day with any, oldest first (All time: the last five years).
records requiredobject
hours requiredProfileHours
weekdays requirednumber[]Listening per weekday, Monday first.
newArtists requiredobject[]

ProfileHours

NameTypeDescription
durationMs requirednumber[]Listening per local hour, midnight first.
timedMs requirednumberListening placed in an hour.
untimedMs requirednumberListening known only to the day (in no hour).
sampled requiredbooleanEstimated from an even sample of the rows (long ranges); otherwise exact.

ProfileItem

NameTypeDescription
plays requiredinteger-9007199254740991–9007199254740991
durationMs requiredinteger-9007199254740991–9007199254740991
rows requiredintegerSource rows counted (rows with a play or listening time). · -9007199254740991–9007199254740991
position requiredinteger-9007199254740991–9007199254740991
entity requiredStatsEntityRef
artworkStatsArtwork | nullThe first items of a page only; null when none is known yet.
line requirednumber[]Milliseconds per line bucket (see lineBuckets).

ProfileListKind

"artist" | "album" | "song"

ProfilePeriod

string

ProfilePeriodInfo

NameTypeDescription
id requiredProfilePeriod
kind required"weeks" | "year" | "all"
label requiredstringPast 4 weeks, the year, or All time.
from requiredstring | nullFirst local day; null for All time.
to requiredstring | nullLast local day (today for current periods).
current requiredbooleanThe period includes today.

ProfileRecent

NameTypeDescription
asOf requiredStatsAsOf
handle requiredstring
today requiredstring
timeZone requiredstring
plays requiredProfileRecentPlay[]
todayTotals requiredobject
artworkPending requiredboolean

ProfileRecentPlay

NameTypeDescription
entity requiredStatsEntityRef | null
title requiredstring
artist requiredstring | null
album requiredstring | null
artwork requiredStatsArtwork | null
day requiredstringLocal day.
at requiredstring | nullWhen it ended (or the middle of a detected window); null when only the day is known.
approximate requiredbooleanThe time is an estimate (a detected play’s window).

ProfileSettings

NameTypeDescription
handle requiredstring | nullnull until the owner picks one.
shared requiredbooleanAnyone with the link can see the profile.
displayName requiredstringThe name the profile shows.
showRecent requiredbooleanThe profile shows recently played songs.
showHistory requiredbooleanThe profile offers last year and All time.
url requiredstring | nullThe profile’s address while shared.
version requiredintegerChanges with every settings change. · -9007199254740991–9007199254740991
previousHandles requiredstring[]Old handles that redirect here.

ProfileTop

NameTypeDescription
asOf requiredStatsAsOf
handle requiredstring
displayName requiredstring
settingsVersion requiredintegerChanges with every profile settings change. · -9007199254740991–9007199254740991
showRecent requiredboolean
showHistory requiredboolean
period requiredProfilePeriodInfo
periods requiredProfilePeriodInfo[]The periods this profile offers, in order.
today requiredstringToday in the account’s time zone.
timeZone requiredstring
since requiredstring | nullFirst local day with listening.
isNew requiredboolean
artworkPending requiredbooleanSome artwork is still being looked up: read again shortly for it.
kind requiredProfileListKind
totals requiredProfileTotals
lineBuckets requiredstring[]
items requiredProfileItem[]
ranks requiredobject

ProfileTotals

NameTypeDescription
durationMs requirednumber
plays requirednumber
days requirednumberDays with listening.
artists requirednumber
albums requirednumber
songs requirednumber
newArtists requirednumberArtists first heard in the period.

Provider

"apple" | "spotify" | "lastfm"

PublicProfile

NameTypeDescription
asOf requiredStatsAsOf
handle requiredstring
displayName requiredstring
settingsVersion requiredintegerChanges with every profile settings change. · -9007199254740991–9007199254740991
showRecent requiredboolean
showHistory requiredboolean
period requiredProfilePeriodInfo
periods requiredProfilePeriodInfo[]The periods this profile offers, in order.
today requiredstringToday in the account’s time zone.
timeZone requiredstring
since requiredstring | nullFirst local day with listening.
isNew requiredboolean
artworkPending requiredbooleanSome artwork is still being looked up: read again shortly for it.
totals requiredProfileTotals
lineBuckets requiredstring[]Each line value’s first day.
artists requiredProfileItem[]
albums requiredProfileItem[]
songs requiredProfileItem[]
trend requiredobject

StatsArtwork

Artwork from the Apple Music catalog. Absent or null: none known yet.

NameTypeDescription
url requiredstringImage URL template with {w} and {h} (pixels).
color requiredstring | nullBackground color, six hex digits.

StatsAsOf

What a result was computed from.

NameTypeDescription
revision requiredinteger-9007199254740991–9007199254740991
metadataRevision requiredinteger-9007199254740991–9007199254740991
timeZone requiredstring
complete requiredbooleanFalse while rows are still being placed in the account time zone.

StatsBucket

"day" | "week" | "month" | "year"

StatsComparison

Two ranges side by side: totals, and how the top artists, albums or songs moved.

NameTypeDescription
asOf requiredStatsAsOf
kind requiredStatsRankingKind
options requiredStatsOptions
providerProvider
a requiredobject
b requiredobject
items requiredStatsComparisonItem[]Each top `limit` of a and b together, in b’s order, then a’s.

StatsComparisonItem

NameTypeDescription
entity requiredStatsEntityRef
a requiredobject
b requiredobject
change required"new" | "gone" | "up" | "down" | "same"From a to b: new (in b’s top only, not heard in a), gone (in a’s top, not heard in b), up, down or same by position; an entity in one top and outside the other counts as up or down.

StatsDiscoveries

Artists, albums or songs first heard in a range.

NameTypeDescription
asOf requiredStatsAsOf
kind requiredStatsRankingKind
range requiredStatsRange
options requiredStatsOptions
providerProvider
items requiredStatsDiscovery[]First heard within the range, ranked by listening within it.
nextCursor requiredstring | null

StatsDiscovery

NameTypeDescription
plays requiredinteger-9007199254740991–9007199254740991
durationMs requiredinteger-9007199254740991–9007199254740991
rows requiredintegerSource rows counted (rows with a play or listening time). · -9007199254740991–9007199254740991
position requiredinteger-9007199254740991–9007199254740991
entity requiredStatsEntityRef
artworkStatsArtwork | nullThe first items of a page only; null when none is known yet.
firstDay requiredstringThe first local day it was ever heard. · pattern ^\d{4}-\d{2}-\d{2}$

StatsEntity

NameTypeDescription
asOf requiredStatsAsOf
entity requiredStatsEntityRef
redirectedFrom requiredstring | nullSet when the requested ID now names this entity.
range requiredStatsRange
options requiredStatsOptions
totals requiredStatsTotals
firstPlayed requiredStatsPlayTime | null
lastPlayed requiredStatsPlayTime | null
providers requiredobject[]
trend requiredobject
top requiredmap of StatsRankingItem[]The top entities of each other kind within this one.

StatsEntityKind

"artist" | "album" | "song" | "recording"

StatsEntityRef

A stable reference to an artist, album, song or recording. IDs survive grouping changes.

NameTypeDescription
id requiredstring
kind requiredStatsEntityKind
title requiredstring
subtitle requiredstring | nullSongs: the source artist. Albums: the album artist.
albumTitlestring | nullSongs: the most listened release's album.
trackIdstring | nullRecordings: the shared track (trk_…) once resolved.

StatsExclusion

One artist, album or song left out of stats.

NameTypeDescription
id requiredstringThe entity ID the rule was saved with.
kind requiredStatsRankingKind
entity requiredStatsEntityRef | nullWhat the ID names now; null while it names no plays (they can return with a later import).
key requiredstring | nullThe entity's key in the history's metadata: a credit key for artists, a grouping key for songs and albums. Null with entity.
grouping requiredExclusionGrouping | nullSongs and albums: which grouping key key is, under the account’s grouping options.

StatsExclusions

The artists, albums and songs left out of stats.

NameTypeDescription
items requiredStatsExclusion[]In the order they were added.
limit requiredintegerMost rules an account can keep. · -9007199254740991–9007199254740991

StatsHistory

NameTypeDescription
asOf requiredStatsAsOf
range requiredStatsRange
providerProviderSet when the request counted one provider only.
within requiredStatsEntityRef | null
items requiredStatsHistoryItem[]
nextCursor requiredstring | nullMore rows follow while this is set.

StatsHistoryItem

A play with its stored columns, placement, time window and chart entities.

NameTypeDescription
id requiredstring
provider requiredProvider
kind requiredMediaKind
day requiredstringpattern ^\d{4}-\d{2}-\d{2}$
listenedAt requiredstringWhen the play started (UTC), as well as it is known (see precision).
durationMs requiredinteger-9007199254740991–9007199254740991
playCount requiredinteger-9007199254740991–9007199254740991
reported requiredPlayReported
durationSource requiredDurationSource
skipped requiredboolean | nullWhether the source said it was skipped; null when it does not say.
title requiredstring
artist requiredstring | null
album requiredstring | null
localDay requiredstringpattern ^\d{4}-\d{2}-\d{2}$
earliestAt requiredstring
latestAt requiredstring
precision requiredTimePrecision
entities requiredobject

StatsOptions

NameTypeDescription
by required"minutes" | "plays"
combineSongVersions requiredboolean
combineAlbumEditions requiredboolean

StatsPeriodTotals

NameTypeDescription
plays requiredinteger-9007199254740991–9007199254740991
durationMs requiredinteger-9007199254740991–9007199254740991
rows requiredintegerSource rows counted (rows with a play or listening time). · -9007199254740991–9007199254740991
days requiredintegerLocal days with any listening. · -9007199254740991–9007199254740991

StatsPlayTime

When a play happened: between earliestAt and latestAt, with the source precision.

NameTypeDescription
localDay requiredstringpattern ^\d{4}-\d{2}-\d{2}$
earliestAt requiredstring
latestAt requiredstring
precision requiredTimePrecision

StatsPoint

NameTypeDescription
start requiredstringFirst local day of the bucket. · pattern ^\d{4}-\d{2}-\d{2}$
plays requiredinteger-9007199254740991–9007199254740991
durationMs requiredinteger-9007199254740991–9007199254740991

StatsRange

Inclusive local days; a null end is open. Both null: all time.

NameTypeDescription
from requiredstring | null
to requiredstring | null

StatsRankingItem

NameTypeDescription
plays requiredinteger-9007199254740991–9007199254740991
durationMs requiredinteger-9007199254740991–9007199254740991
rows requiredintegerSource rows counted (rows with a play or listening time). · -9007199254740991–9007199254740991
position requiredinteger-9007199254740991–9007199254740991
entity requiredStatsEntityRef
artworkStatsArtwork | nullThe first items of a page only; null when none is known yet.

StatsRankingKind

"artist" | "album" | "song"

StatsRankings

NameTypeDescription
asOf requiredStatsAsOf
kind requiredStatsRankingKind
range requiredStatsRange
options requiredStatsOptions
providerProviderSet when the request counted one provider only.
within requiredStatsEntityRef | null
items requiredStatsRankingItem[]
nextCursor requiredstring | null

StatsResolved

NameTypeDescription
asOf requiredStatsAsOf
redirectedFrom requiredstring | null
recordings requiredStatsEntityRef[]
songs requiredStatsEntityRef[]
albums requiredStatsEntityRef[]
artists requiredStatsEntityRef[]

StatsSearch

NameTypeDescription
asOf requiredStatsAsOf
query requiredstring
results requiredmap of StatsRankingItem[]

StatsSummary

A range at a glance: totals, the top artists, albums and songs, the trend, the peak and listening by hour.

NameTypeDescription
asOf requiredStatsAsOf
range requiredStatsRange
options requiredStatsOptions
providerProviderSet when the request counted one provider only.
totals requiredStatsPeriodTotals
firstDay requiredstring | nullThe first local day with listening in the range.
lastDay requiredstring | nullThe last local day with listening in the range.
artists requiredStatsRankingItem[]
albums requiredStatsRankingItem[]
songs requiredStatsRankingItem[]
trend requiredobject
peak requiredStatsPoint | nullThe bucket with the most listening (by the ranking measure).
hours requiredProfileHours

StatsTotals

NameTypeDescription
plays requiredinteger-9007199254740991–9007199254740991
durationMs requiredinteger-9007199254740991–9007199254740991
rows requiredintegerSource rows counted (rows with a play or listening time). · -9007199254740991–9007199254740991

StatsTrend

NameTypeDescription
asOf requiredStatsAsOf
range requiredStatsRange
bucket requiredStatsBucket
weekStart requiredintegerFirst day of week buckets: 0 Sunday … 6 Saturday. · 0–6
providerProviderSet when the request counted one provider only.
within requiredStatsEntityRef | null
points requiredStatsPoint[]
nextCursor requiredstring | null

TimePrecision

How exactly the source dates a play: an exact time, a window between two times, an hour, or a day.

"exact" | "window" | "hour" | "day"