ListenBrainz — Tool Reference
Track listening history, manage playlists, get recommendations, and discover music via ListenBrainz
Your AI assistant discovers and invokes these tools through the
MCP Server's meta tools —
it calls execute_tool with the tool name and arguments below.
# add_tracks_to_playlist write
Add tracks to an existing playlist by MBID, by fuzzy (artist, title) ref, or both.
Full description
Add tracks to an existing playlist.
Args:
playlist_mbid: Playlist MBID
recording_mbids: List of MusicBrainz recording MBIDs to add (optional)
tracks: Unified list of MbidTrack or FuzzyTrack (optional)
on_unresolved: skip / fail / include_placeholder | Parameter | Type | Required | Description |
|---|---|---|---|
playlist_mbid | string | Yes | Playlist MBID |
recording_mbids | array<string> | null | No | List of recording MBIDs to add. Back-compat entrypoint; new callers should prefer `tracks` which also accepts fuzzy refs. |
tracks | array<MbidTrack | FuzzyTrack> | null | No | Unified track list. `{mbid: ...}` or `{artist, title, album?}`. Fuzzy entries are resolved to MBIDs via metadata_lookup before being appended to the playlist. |
on_unresolved | "skip" | "fail" | "include_placeholder" | No | How to handle unresolvable FuzzyTracks. Same semantics as on create_playlist (skip / fail / include_placeholder). Default: "skip" |
Parameter schema (JSON)
{
"$defs": {
"FuzzyTrack": {
"additionalProperties": false,
"description": "Track reference by (artist, title[, album]) — the natural-language form.\n\nResolved to a recording MBID via `metadata_lookup` (pg_trgm similarity\nagainst the local MB replica, with a global-listen-count tiebreaker\nfor ambiguous matches).",
"properties": {
"artist": {
"description": "Artist name, e.g. 'Queen'",
"title": "Artist",
"type": "string"
},
"title": {
"description": "Recording/track title, e.g. 'Bohemian Rhapsody'",
"title": "Title",
"type": "string"
},
"album": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Release (album) name — optional, narrows ambiguous matches",
"title": "Album"
}
},
"required": [
"artist",
"title"
],
"title": "FuzzyTrack",
"type": "object"
},
"MbidTrack": {
"additionalProperties": false,
"description": "Track reference by recording MBID — the precise form.",
"properties": {
"mbid": {
"description": "MusicBrainz recording MBID",
"title": "Mbid",
"type": "string"
}
},
"required": [
"mbid"
],
"title": "MbidTrack",
"type": "object"
}
},
"properties": {
"playlist_mbid": {
"description": "Playlist MBID",
"title": "Playlist Mbid",
"type": "string"
},
"recording_mbids": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "List of recording MBIDs to add. Back-compat entrypoint; new callers should prefer `tracks` which also accepts fuzzy refs.",
"title": "Recording Mbids"
},
"tracks": {
"anyOf": [
{
"items": {
"anyOf": [
{
"$ref": "#/$defs/MbidTrack"
},
{
"$ref": "#/$defs/FuzzyTrack"
}
]
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "Unified track list. `{mbid: ...}` or `{artist, title, album?}`. Fuzzy entries are resolved to MBIDs via metadata_lookup before being appended to the playlist.",
"title": "Tracks"
},
"on_unresolved": {
"default": "skip",
"description": "How to handle unresolvable FuzzyTracks. Same semantics as on create_playlist (skip / fail / include_placeholder).",
"enum": [
"skip",
"fail",
"include_placeholder"
],
"title": "On Unresolved",
"type": "string"
}
},
"required": [
"playlist_mbid"
],
"title": "AddTracksToPlaylistParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"ResolutionReport": {
"description": "Summary of FuzzyTrack resolution for a playlist-write call.\n\nIncluded on `create_playlist` / `add_tracks_to_playlist` responses\nwhenever the caller supplied a `tracks` list, AND on cross-service\nexports (`source=spotify` / `apple_music` / `soundcloud`) when LB's\nupstream resolver silently drops tracks during the export step\n(toolforest_tools#1270).",
"properties": {
"total_requested": {
"description": "Total TrackRefs supplied (MbidTrack + FuzzyTrack)",
"title": "Total Requested",
"type": "integer"
},
"resolved": {
"description": "Number that made it into the playlist",
"title": "Resolved",
"type": "integer"
},
"unresolved": {
"description": "FuzzyTracks that couldn't be resolved",
"items": {
"$ref": "#/$defs/UnresolvedEntry"
},
"title": "Unresolved",
"type": "array"
},
"notes": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Human-readable note about the situation. Surfaced when the underlying API doesn't tell us per-track reasons (e.g. LB's MBID→Spotify resolver silently drops tracks). LLM agents should relay this to users verbatim — it explains what failed, why, and whether a fix is in flight upstream.",
"title": "Notes"
}
},
"required": [
"total_requested",
"resolved"
],
"title": "ResolutionReport",
"type": "object"
},
"UnresolvedEntry": {
"description": "A FuzzyTrack that couldn't be resolved to an MBID (or was downgraded\nfrom placeholder to skip because the source doesn't accept placeholders).",
"properties": {
"query": {
"additionalProperties": true,
"description": "Original FuzzyTrack fields",
"title": "Query",
"type": "object"
},
"reason": {
"description": "'no_match' | 'placeholder_unsupported_for_external_source'",
"title": "Reason",
"type": "string"
}
},
"required": [
"query",
"reason"
],
"title": "UnresolvedEntry",
"type": "object"
}
},
"properties": {
"success": {
"title": "Success",
"type": "boolean"
},
"resolution_report": {
"anyOf": [
{
"$ref": "#/$defs/ResolutionReport"
},
{
"type": "null"
}
],
"default": null
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Error"
}
},
"required": [
"success"
],
"title": "AddTracksToPlaylistResult",
"type": "object"
} # artist_lookup read
Look up MusicBrainz artist MBIDs for a batch of artist names. Exact case-insensitive match first, trigram similarity fallback for typos. Pair with get_similar_artists when you only have names.
Full description
Resolve artist names to MusicBrainz artist MBIDs.
Useful when you have artist names (from get_top_artists, a user
input, or external data) and need MBIDs to call MBID-keyed tools
like get_similar_artists or MusicBrainz APIs directly.
Resolution path (server-side on the brainz API):
1. Exact case-insensitive match against the MusicBrainz schema.
When multiple MB artists share a name, returns the
earliest-created (lowest artist.id) and tags the result as
ambiguous=True.
2. Trigram similarity fallback (pg_trgm) for names that don't
match exactly — handles typos and minor variations. Only
matches with similarity ≥ 0.6 are returned; lower scores
surface as match=None rather than wrong guesses.
The trigram tier runs in parallel server-side, capped at 20
fallbacks per batch.
Args:
names: List of artist names (max 50)
Returns:
ArtistLookupResult.results — one entry per input name, in
input order. Each entry has:
- query: the input name
- match: {name, mbid, method} or None
- ambiguous: True when multiple exact matches existed | Parameter | Type | Required | Description |
|---|---|---|---|
names | array<string> | null | No | Artist names to resolve to MusicBrainz artist MBIDs. Batch-capable. Resolution is case-insensitive exact match first, then a trigram similarity fallback for typos or minor variations. Max 50 per call. Provide this OR `artist_names`. |
artist_names | array<string> | null | No | Alias for `names` for consistency with sibling tools that use compound names. Provide this OR `names`. |
Parameter schema (JSON)
{
"description": "Parameters for artist_lookup",
"properties": {
"names": {
"anyOf": [
{
"items": {
"type": "string"
},
"maxItems": 50,
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "Artist names to resolve to MusicBrainz artist MBIDs. Batch-capable. Resolution is case-insensitive exact match first, then a trigram similarity fallback for typos or minor variations. Max 50 per call. Provide this OR `artist_names`.",
"title": "Names"
},
"artist_names": {
"anyOf": [
{
"items": {
"type": "string"
},
"maxItems": 50,
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "Alias for `names` for consistency with sibling tools that use compound names. Provide this OR `names`.",
"title": "Artist Names"
}
},
"title": "ArtistLookupParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"ArtistLookupMatch": {
"description": "A matched MusicBrainz artist row.",
"properties": {
"name": {
"description": "Artist name as stored in MusicBrainz",
"title": "Name",
"type": "string"
},
"mbid": {
"description": "MusicBrainz artist MBID (UUID)",
"title": "Mbid",
"type": "string"
},
"method": {
"description": "How the match was produced: 'exact' = case-insensitive name match; 'trigram' = pg_trgm similarity fallback. Trigram matches may not be the artist the caller intended.",
"enum": [
"exact",
"trigram"
],
"title": "Method",
"type": "string"
}
},
"required": [
"name",
"mbid",
"method"
],
"title": "ArtistLookupMatch",
"type": "object"
},
"ArtistLookupResultItem": {
"description": "One result row per input name, in input order.",
"properties": {
"query": {
"description": "The input name as provided",
"title": "Query",
"type": "string"
},
"match": {
"anyOf": [
{
"$ref": "#/$defs/ArtistLookupMatch"
},
{
"type": "null"
}
],
"default": null,
"description": "Best matching MusicBrainz artist, or None if neither exact nor trigram-similarity match succeeded. When multiple MB artists share the queried name (e.g. two bands called 'Queen'), we return the earliest-created one and set ambiguous=true."
},
"ambiguous": {
"default": false,
"description": "True when multiple MusicBrainz artists exact-match the queried name. The match field holds the earliest-created of them; callers who care about disambiguation should surface a 'did you mean' UX.",
"title": "Ambiguous",
"type": "boolean"
}
},
"required": [
"query"
],
"title": "ArtistLookupResultItem",
"type": "object"
}
},
"description": "Result from artist_lookup",
"properties": {
"results": {
"description": "One entry per input name, preserving input order.",
"items": {
"$ref": "#/$defs/ArtistLookupResultItem"
},
"title": "Results",
"type": "array"
}
},
"required": [
"results"
],
"title": "ArtistLookupResult",
"type": "object"
} # copy_playlist write
Copy an existing playlist into a new one titled 'Copy of <original>'.
Full description
Copy a playlist. Creates a new playlist titled 'Copy of <original>'.
Args:
playlist_mbid: Playlist MBID to copy | Parameter | Type | Required | Description |
|---|---|---|---|
playlist_mbid | string | Yes | Playlist MBID to copy |
Parameter schema (JSON)
{
"properties": {
"playlist_mbid": {
"description": "Playlist MBID to copy",
"title": "Playlist Mbid",
"type": "string"
}
},
"required": [
"playlist_mbid"
],
"title": "CopyPlaylistParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"properties": {
"success": {
"title": "Success",
"type": "boolean"
},
"new_playlist_mbid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "New Playlist Mbid"
},
"playlist_url": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Shareable URL for the newly-created copy on listenbrainz.org.",
"title": "Playlist Url"
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Error"
}
},
"required": [
"success"
],
"title": "CopyPlaylistResult",
"type": "object"
} # create_playlist write
Create a new playlist on ListenBrainz, or on a connected Spotify, Apple Music, or SoundCloud account via the source parameter.
Full description
Create a new playlist on ListenBrainz or an external service.
For external services (spotify, apple_music, soundcloud), the playlist is
created directly on the target platform using recording MBIDs. Requires
the service to be connected on the ListenBrainz side.
Args:
source: Target platform (listenbrainz, spotify, apple_music, soundcloud)
title: Playlist title
description: Playlist description (optional)
recording_mbids: List of MusicBrainz recording MBIDs to add (optional)
tracks: Unified list of MbidTrack or FuzzyTrack. Fuzzy entries are
resolved to MBIDs via metadata_lookup before creation.
on_unresolved: How to handle unresolved FuzzyTracks — skip (default),
fail, or include_placeholder (LB source only)
public: Whether the playlist is public (default true) | Parameter | Type | Required | Description |
|---|---|---|---|
source | string | No | Target platform: listenbrainz, spotify, apple_music, soundcloud Default: "listenbrainz" |
title | string | Yes | Playlist title |
description | string | null | No | Playlist description |
recording_mbids | array<string> | null | No | List of recording MBIDs to add. Back-compat entrypoint — new callers should use `tracks` which accepts either MBIDs or fuzzy (artist, title) refs. Both can be supplied; MBIDs are concatenated first, then resolved tracks, with dedup. |
tracks | array<MbidTrack | FuzzyTrack> | null | No | Unified track list. Each entry is either `{mbid: ...}` or `{artist: ..., title: ..., album?: ...}`. Fuzzy entries are resolved to MBIDs via metadata_lookup before playlist create. |
on_unresolved | "skip" | "fail" | "include_placeholder" | No | How to handle FuzzyTracks that didn't resolve. `skip` (default): drop them, report in resolution_report. `fail`: raise ValidationError with all unresolved entries; playlist is NOT created. `include_placeholder`: LB source only — emit a title+creator track with no identifier; external sources downgrade to skip. Default: "skip" |
public | boolean | No | Whether the playlist is public Default: true |
Parameter schema (JSON)
{
"$defs": {
"FuzzyTrack": {
"additionalProperties": false,
"description": "Track reference by (artist, title[, album]) — the natural-language form.\n\nResolved to a recording MBID via `metadata_lookup` (pg_trgm similarity\nagainst the local MB replica, with a global-listen-count tiebreaker\nfor ambiguous matches).",
"properties": {
"artist": {
"description": "Artist name, e.g. 'Queen'",
"title": "Artist",
"type": "string"
},
"title": {
"description": "Recording/track title, e.g. 'Bohemian Rhapsody'",
"title": "Title",
"type": "string"
},
"album": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Release (album) name — optional, narrows ambiguous matches",
"title": "Album"
}
},
"required": [
"artist",
"title"
],
"title": "FuzzyTrack",
"type": "object"
},
"MbidTrack": {
"additionalProperties": false,
"description": "Track reference by recording MBID — the precise form.",
"properties": {
"mbid": {
"description": "MusicBrainz recording MBID",
"title": "Mbid",
"type": "string"
}
},
"required": [
"mbid"
],
"title": "MbidTrack",
"type": "object"
}
},
"properties": {
"source": {
"default": "listenbrainz",
"description": "Target platform: listenbrainz, spotify, apple_music, soundcloud",
"title": "Source",
"type": "string"
},
"title": {
"description": "Playlist title",
"title": "Title",
"type": "string"
},
"description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Playlist description",
"title": "Description"
},
"recording_mbids": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "List of recording MBIDs to add. Back-compat entrypoint — new callers should use `tracks` which accepts either MBIDs or fuzzy (artist, title) refs. Both can be supplied; MBIDs are concatenated first, then resolved tracks, with dedup.",
"title": "Recording Mbids"
},
"tracks": {
"anyOf": [
{
"items": {
"anyOf": [
{
"$ref": "#/$defs/MbidTrack"
},
{
"$ref": "#/$defs/FuzzyTrack"
}
]
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "Unified track list. Each entry is either `{mbid: ...}` or `{artist: ..., title: ..., album?: ...}`. Fuzzy entries are resolved to MBIDs via metadata_lookup before playlist create.",
"title": "Tracks"
},
"on_unresolved": {
"default": "skip",
"description": "How to handle FuzzyTracks that didn't resolve. `skip` (default): drop them, report in resolution_report. `fail`: raise ValidationError with all unresolved entries; playlist is NOT created. `include_placeholder`: LB source only — emit a title+creator track with no identifier; external sources downgrade to skip.",
"enum": [
"skip",
"fail",
"include_placeholder"
],
"title": "On Unresolved",
"type": "string"
},
"public": {
"default": true,
"description": "Whether the playlist is public",
"title": "Public",
"type": "boolean"
}
},
"required": [
"title"
],
"title": "CreatePlaylistParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"ResolutionReport": {
"description": "Summary of FuzzyTrack resolution for a playlist-write call.\n\nIncluded on `create_playlist` / `add_tracks_to_playlist` responses\nwhenever the caller supplied a `tracks` list, AND on cross-service\nexports (`source=spotify` / `apple_music` / `soundcloud`) when LB's\nupstream resolver silently drops tracks during the export step\n(toolforest_tools#1270).",
"properties": {
"total_requested": {
"description": "Total TrackRefs supplied (MbidTrack + FuzzyTrack)",
"title": "Total Requested",
"type": "integer"
},
"resolved": {
"description": "Number that made it into the playlist",
"title": "Resolved",
"type": "integer"
},
"unresolved": {
"description": "FuzzyTracks that couldn't be resolved",
"items": {
"$ref": "#/$defs/UnresolvedEntry"
},
"title": "Unresolved",
"type": "array"
},
"notes": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Human-readable note about the situation. Surfaced when the underlying API doesn't tell us per-track reasons (e.g. LB's MBID→Spotify resolver silently drops tracks). LLM agents should relay this to users verbatim — it explains what failed, why, and whether a fix is in flight upstream.",
"title": "Notes"
}
},
"required": [
"total_requested",
"resolved"
],
"title": "ResolutionReport",
"type": "object"
},
"UnresolvedEntry": {
"description": "A FuzzyTrack that couldn't be resolved to an MBID (or was downgraded\nfrom placeholder to skip because the source doesn't accept placeholders).",
"properties": {
"query": {
"additionalProperties": true,
"description": "Original FuzzyTrack fields",
"title": "Query",
"type": "object"
},
"reason": {
"description": "'no_match' | 'placeholder_unsupported_for_external_source'",
"title": "Reason",
"type": "string"
}
},
"required": [
"query",
"reason"
],
"title": "UnresolvedEntry",
"type": "object"
}
},
"properties": {
"success": {
"title": "Success",
"type": "boolean"
},
"playlist_mbid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Playlist Mbid"
},
"playlist_url": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Shareable URL for the newly-created playlist on listenbrainz.org. Populated only for the listenbrainz source; external sources put their URL in `external_url` instead.",
"title": "Playlist Url"
},
"external_url": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "External Url"
},
"tracks_requested": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Tracks Requested"
},
"tracks_in_exported_playlist": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Tracks In Exported Playlist"
},
"resolution_report": {
"anyOf": [
{
"$ref": "#/$defs/ResolutionReport"
},
{
"type": "null"
}
],
"default": null
},
"description_set": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"title": "Description Set"
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Error"
}
},
"required": [
"success"
],
"title": "CreatePlaylistResult",
"type": "object"
} # delete_listen write
Delete a specific listen from history by timestamp and MSID, scheduling removal for the next hour.
Full description
Delete a listen from the user's history.
The deletion is scheduled and processed at the top of the next hour.
Requires both the Unix timestamp and recording MSID of the listen.
Args:
listened_at: Unix timestamp of the listen to delete
recording_msid: MessyBrainz recording MSID (available in listen metadata) | Parameter | Type | Required | Description |
|---|---|---|---|
listened_at | integer | Yes | Unix timestamp of the listen to delete |
recording_msid | string | Yes | MessyBrainz recording MSID (from listen metadata) |
Parameter schema (JSON)
{
"properties": {
"listened_at": {
"description": "Unix timestamp of the listen to delete",
"title": "Listened At",
"type": "integer"
},
"recording_msid": {
"description": "MessyBrainz recording MSID (from listen metadata)",
"title": "Recording Msid",
"type": "string"
}
},
"required": [
"listened_at",
"recording_msid"
],
"title": "DeleteListenParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"properties": {
"success": {
"title": "Success",
"type": "boolean"
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Error"
}
},
"required": [
"success"
],
"title": "DeleteListenResult",
"type": "object"
} # delete_playlist write
Permanently delete a playlist. Only the creator can delete it.
Full description
Permanently delete a playlist. Only the creator can delete it.
Args:
playlist_mbid: Playlist MBID to delete | Parameter | Type | Required | Description |
|---|---|---|---|
playlist_mbid | string | Yes | Playlist MBID to delete |
Parameter schema (JSON)
{
"properties": {
"playlist_mbid": {
"description": "Playlist MBID to delete",
"title": "Playlist Mbid",
"type": "string"
}
},
"required": [
"playlist_mbid"
],
"title": "DeletePlaylistParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"properties": {
"success": {
"title": "Success",
"type": "boolean"
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Error"
}
},
"required": [
"success"
],
"title": "DeletePlaylistResult",
"type": "object"
} # export_playlist write
Export an existing ListenBrainz playlist to Spotify, Apple Music, or SoundCloud.
Full description
Export an existing ListenBrainz playlist to an external service (Spotify/Apple Music/SoundCloud).
Requires the target service to be connected on the ListenBrainz side.
Use get_connected_services to check first.
Args:
playlist_mbid: ListenBrainz playlist MBID to export
service: Target service ('spotify', 'apple_music', or 'soundcloud')
is_public: Whether the exported playlist should be public | Parameter | Type | Required | Description |
|---|---|---|---|
playlist_mbid | string | Yes | ListenBrainz playlist MBID to export |
service | string | Yes | Target service: spotify, apple_music, or soundcloud |
is_public | boolean | No | Whether the exported playlist should be public Default: true |
Parameter schema (JSON)
{
"properties": {
"playlist_mbid": {
"description": "ListenBrainz playlist MBID to export",
"title": "Playlist Mbid",
"type": "string"
},
"service": {
"description": "Target service: spotify, apple_music, or soundcloud",
"title": "Service",
"type": "string"
},
"is_public": {
"default": true,
"description": "Whether the exported playlist should be public",
"title": "Is Public",
"type": "boolean"
}
},
"required": [
"playlist_mbid",
"service"
],
"title": "ExportPlaylistParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"properties": {
"success": {
"title": "Success",
"type": "boolean"
},
"external_url": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "External Url"
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Error"
}
},
"required": [
"success"
],
"title": "ExportPlaylistResult",
"type": "object"
} # get_all_listens read
List up to 1000 listens across paginated history and return full listen records with cursor timestamps.
Full description
Get all listens by paginating through the listen history.
Uses timestamp-cursor pagination. Returns up to 1000 listens.
If the result is truncated, use oldest_listen_ts as max_ts in a follow-up
call to fetch older listens.
Timestamps (`listened_at`, `min_ts`, `max_ts`, and the
`oldest_listen_ts` / `newest_listen_ts` cursors) are Unix epoch
seconds — zone-agnostic integers. When grouping by day/hour or
rendering wall-clock dates for a user, apply their IANA timezone
explicitly; the same instant is "Friday 9pm" in Pacific and
"Saturday 04:00" in UTC.
Args:
min_ts: Return listens after this Unix timestamp UTC seconds (optional)
max_ts: Return listens before this Unix timestamp UTC seconds (optional)
username: ListenBrainz username (defaults to authenticated user) | Parameter | Type | Required | Description |
|---|---|---|---|
min_ts | integer | null | No | Return listens after this Unix timestamp |
max_ts | integer | null | No | Return listens before this Unix timestamp |
username | string | null | No | ListenBrainz username to query. Defaults to the authenticated user. |
Parameter schema (JSON)
{
"properties": {
"min_ts": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Return listens after this Unix timestamp",
"title": "Min Ts"
},
"max_ts": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Return listens before this Unix timestamp",
"title": "Max Ts"
},
"username": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "ListenBrainz username to query. Defaults to the authenticated user.",
"title": "Username"
}
},
"title": "GetAllListensParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"Listen": {
"properties": {
"track_name": {
"title": "Track Name",
"type": "string"
},
"artist_name": {
"title": "Artist Name",
"type": "string"
},
"release_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Release Name"
},
"listened_at": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Listened At"
},
"recording_mbid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Recording Mbid"
},
"artist_mbids": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Artist Mbids"
},
"genres": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Genres"
},
"release_group_type": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Release Group Type"
}
},
"required": [
"track_name",
"artist_name"
],
"title": "Listen",
"type": "object"
}
},
"properties": {
"success": {
"title": "Success",
"type": "boolean"
},
"listens": {
"anyOf": [
{
"items": {
"$ref": "#/$defs/Listen"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Listens"
},
"total_count": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Total Count"
},
"truncated": {
"default": false,
"title": "Truncated",
"type": "boolean"
},
"oldest_listen_ts": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Oldest Listen Ts"
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Error"
}
},
"required": [
"success"
],
"title": "GetAllListensResult",
"type": "object"
} # get_all_top_albums read
List up to 1000 top albums across all stats pages and return the full ranked album dataset.
Full description
Get all top albums by paginating through the full stats list.
Automatically handles pagination, returning up to 1000 albums.
For most users this returns the complete dataset.
Args:
range: Time window. Defaults to 'all_time'. Note that LB-aligned
tokens like 'week'/'month'/'year' mean the PREVIOUS FULL
calendar period, not the last N days.
username: ListenBrainz username (defaults to authenticated user) | Parameter | Type | Required | Description |
|---|---|---|---|
range | "week" | "month" | "quarter" | "half_yearly" | "year" | "all_time" | "this_week" | "this_month" | "this_year" | No | Time window for the full top-albums ranking. Previous full calendar period (LB-aligned): 'week', 'month', 'quarter', 'half_yearly', 'year'. Current period (period start to now): 'this_week', 'this_month', 'this_year'. Open-ended: 'all_time'. All period boundaries are UTC-aligned. For users in non-UTC zones the boundary instant differs from local midnight — e.g. 'this_week' for a Pacific user starts at Sunday 17:00 local (Monday 00:00 UTC). This is rarely worth correcting for ranking endpoints (a few hours' worth of listens won't reorder a top-N), but matters for tools that bin per-listen. get_daily_activity exposes a `timezone` parameter that actually shifts bucket alignment to the user's zone — pass it when known. get_listening_activity accepts the same param for consistency but its time-series bucket boundaries remain UTC-aligned regardless (rebinning is deferred work — use get_recent_listens + client-side bucketing if exact local boundaries matter). Note: 'week'/'month'/'year' mean the previous FULL calendar period, not the last N days. Default: "all_time" |
username | string | null | No | ListenBrainz username to query. Defaults to the authenticated user. |
Parameter schema (JSON)
{
"properties": {
"range": {
"default": "all_time",
"description": "Time window for the full top-albums ranking. Previous full calendar period (LB-aligned): 'week', 'month', 'quarter', 'half_yearly', 'year'. Current period (period start to now): 'this_week', 'this_month', 'this_year'. Open-ended: 'all_time'. All period boundaries are UTC-aligned. For users in non-UTC zones the boundary instant differs from local midnight — e.g. 'this_week' for a Pacific user starts at Sunday 17:00 local (Monday 00:00 UTC). This is rarely worth correcting for ranking endpoints (a few hours' worth of listens won't reorder a top-N), but matters for tools that bin per-listen. get_daily_activity exposes a `timezone` parameter that actually shifts bucket alignment to the user's zone — pass it when known. get_listening_activity accepts the same param for consistency but its time-series bucket boundaries remain UTC-aligned regardless (rebinning is deferred work — use get_recent_listens + client-side bucketing if exact local boundaries matter). Note: 'week'/'month'/'year' mean the previous FULL calendar period, not the last N days.",
"enum": [
"week",
"month",
"quarter",
"half_yearly",
"year",
"all_time",
"this_week",
"this_month",
"this_year"
],
"title": "Range",
"type": "string"
},
"username": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "ListenBrainz username to query. Defaults to the authenticated user.",
"title": "Username"
}
},
"title": "GetAllTopAlbumsParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"DataWindow": {
"description": "Surfaces coverage information when a stats response may not\nfully cover the requested time range — most commonly when the\nresponse is empty and the LLM agent should retry with a different\n`range` token.\n\nPopulated on stats responses (top_artists/albums/tracks,\nlisten_count, daily/listening activity, etc.) by\n`_build_data_window` per toolforest_tools#1294. Currently\npopulated only for empty-response cases; see issue body for the\n\"obviously truncated but non-empty\" follow-up case (rolling-token\ncache wall) which is intentionally deferred.",
"properties": {
"requested_range": {
"description": "The `range` token the caller passed in.",
"title": "Requested Range",
"type": "string"
},
"note": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Human-readable explanation of why the response may not fully cover the requested range. LLM agents should relay this verbatim and consider the suggested retry. Common case: empty response because the user asked for `year` (previous full calendar year) but is a new account with no listens in that window — retry with `this_year`.",
"title": "Note"
}
},
"required": [
"requested_range"
],
"title": "DataWindow",
"type": "object"
},
"TopEntity": {
"description": "Shared model for top artists, albums, tracks.\n\n`mbid` holds the primary MusicBrainz identifier for the entity:\n - top artists → artist MBID\n - top albums → release MBID\n - top tracks → recording MBID\n\n`mbid` is `null` whenever the underlying listen records did not\ncarry a MusicBrainz ID. This is common in practice: scrobbles from\nSpotify and other connected services often arrive without MBIDs,\nand ListenBrainz does not back-fill them on the stats path. If you\nneed MBIDs to feed into MBID-keyed tools (get_similar_artists,\nadd_tracks_to_playlist, etc.), resolve names via\nlistenbrainz-artist_lookup or listenbrainz-metadata_lookup first\nrather than treating null `mbid` as a hard failure.\n\n`artist_mbid` is a convenience secondary field populated for top\nalbums and top tracks (where the primary `mbid` is the release or\nrecording MBID) so callers can resolve the artist without a second\ncall. For top artists it's the same value as `mbid`.",
"properties": {
"name": {
"title": "Name",
"type": "string"
},
"listen_count": {
"title": "Listen Count",
"type": "integer"
},
"artist_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Artist Name"
},
"release_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Release Name"
},
"mbid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Mbid"
},
"artist_mbid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Artist Mbid"
}
},
"required": [
"name",
"listen_count"
],
"title": "TopEntity",
"type": "object"
}
},
"properties": {
"success": {
"title": "Success",
"type": "boolean"
},
"albums": {
"anyOf": [
{
"items": {
"$ref": "#/$defs/TopEntity"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Albums"
},
"total_count": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Total Count"
},
"truncated": {
"default": false,
"title": "Truncated",
"type": "boolean"
},
"data_window": {
"anyOf": [
{
"$ref": "#/$defs/DataWindow"
},
{
"type": "null"
}
],
"default": null
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Error"
}
},
"required": [
"success"
],
"title": "GetAllTopAlbumsResult",
"type": "object"
} # get_all_top_artists read
List up to 1000 top artists across all stats pages and return the full ranked artist dataset.
Full description
Get all top artists by paginating through the full stats list.
Automatically handles pagination, returning up to 1000 artists.
For most users this returns the complete dataset.
Args:
range: Time window. Defaults to 'all_time'. Note that LB-aligned
tokens like 'week'/'month'/'year' mean the PREVIOUS FULL
calendar period, not the last N days.
username: ListenBrainz username (defaults to authenticated user) | Parameter | Type | Required | Description |
|---|---|---|---|
range | "week" | "month" | "quarter" | "half_yearly" | "year" | "all_time" | "this_week" | "this_month" | "this_year" | No | Time window for the full top-artists ranking. Previous full calendar period (LB-aligned): 'week', 'month', 'quarter', 'half_yearly', 'year'. Current period (period start to now): 'this_week', 'this_month', 'this_year'. Open-ended: 'all_time'. All period boundaries are UTC-aligned. For users in non-UTC zones the boundary instant differs from local midnight — e.g. 'this_week' for a Pacific user starts at Sunday 17:00 local (Monday 00:00 UTC). This is rarely worth correcting for ranking endpoints (a few hours' worth of listens won't reorder a top-N), but matters for tools that bin per-listen. get_daily_activity exposes a `timezone` parameter that actually shifts bucket alignment to the user's zone — pass it when known. get_listening_activity accepts the same param for consistency but its time-series bucket boundaries remain UTC-aligned regardless (rebinning is deferred work — use get_recent_listens + client-side bucketing if exact local boundaries matter). Note: 'week'/'month'/'year' mean the previous FULL calendar period, not the last N days. Default: "all_time" |
username | string | null | No | ListenBrainz username to query. Defaults to the authenticated user. |
Parameter schema (JSON)
{
"properties": {
"range": {
"default": "all_time",
"description": "Time window for the full top-artists ranking. Previous full calendar period (LB-aligned): 'week', 'month', 'quarter', 'half_yearly', 'year'. Current period (period start to now): 'this_week', 'this_month', 'this_year'. Open-ended: 'all_time'. All period boundaries are UTC-aligned. For users in non-UTC zones the boundary instant differs from local midnight — e.g. 'this_week' for a Pacific user starts at Sunday 17:00 local (Monday 00:00 UTC). This is rarely worth correcting for ranking endpoints (a few hours' worth of listens won't reorder a top-N), but matters for tools that bin per-listen. get_daily_activity exposes a `timezone` parameter that actually shifts bucket alignment to the user's zone — pass it when known. get_listening_activity accepts the same param for consistency but its time-series bucket boundaries remain UTC-aligned regardless (rebinning is deferred work — use get_recent_listens + client-side bucketing if exact local boundaries matter). Note: 'week'/'month'/'year' mean the previous FULL calendar period, not the last N days.",
"enum": [
"week",
"month",
"quarter",
"half_yearly",
"year",
"all_time",
"this_week",
"this_month",
"this_year"
],
"title": "Range",
"type": "string"
},
"username": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "ListenBrainz username to query. Defaults to the authenticated user.",
"title": "Username"
}
},
"title": "GetAllTopArtistsParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"DataWindow": {
"description": "Surfaces coverage information when a stats response may not\nfully cover the requested time range — most commonly when the\nresponse is empty and the LLM agent should retry with a different\n`range` token.\n\nPopulated on stats responses (top_artists/albums/tracks,\nlisten_count, daily/listening activity, etc.) by\n`_build_data_window` per toolforest_tools#1294. Currently\npopulated only for empty-response cases; see issue body for the\n\"obviously truncated but non-empty\" follow-up case (rolling-token\ncache wall) which is intentionally deferred.",
"properties": {
"requested_range": {
"description": "The `range` token the caller passed in.",
"title": "Requested Range",
"type": "string"
},
"note": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Human-readable explanation of why the response may not fully cover the requested range. LLM agents should relay this verbatim and consider the suggested retry. Common case: empty response because the user asked for `year` (previous full calendar year) but is a new account with no listens in that window — retry with `this_year`.",
"title": "Note"
}
},
"required": [
"requested_range"
],
"title": "DataWindow",
"type": "object"
},
"TopEntity": {
"description": "Shared model for top artists, albums, tracks.\n\n`mbid` holds the primary MusicBrainz identifier for the entity:\n - top artists → artist MBID\n - top albums → release MBID\n - top tracks → recording MBID\n\n`mbid` is `null` whenever the underlying listen records did not\ncarry a MusicBrainz ID. This is common in practice: scrobbles from\nSpotify and other connected services often arrive without MBIDs,\nand ListenBrainz does not back-fill them on the stats path. If you\nneed MBIDs to feed into MBID-keyed tools (get_similar_artists,\nadd_tracks_to_playlist, etc.), resolve names via\nlistenbrainz-artist_lookup or listenbrainz-metadata_lookup first\nrather than treating null `mbid` as a hard failure.\n\n`artist_mbid` is a convenience secondary field populated for top\nalbums and top tracks (where the primary `mbid` is the release or\nrecording MBID) so callers can resolve the artist without a second\ncall. For top artists it's the same value as `mbid`.",
"properties": {
"name": {
"title": "Name",
"type": "string"
},
"listen_count": {
"title": "Listen Count",
"type": "integer"
},
"artist_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Artist Name"
},
"release_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Release Name"
},
"mbid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Mbid"
},
"artist_mbid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Artist Mbid"
}
},
"required": [
"name",
"listen_count"
],
"title": "TopEntity",
"type": "object"
}
},
"properties": {
"success": {
"title": "Success",
"type": "boolean"
},
"artists": {
"anyOf": [
{
"items": {
"$ref": "#/$defs/TopEntity"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Artists"
},
"total_count": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Total Count"
},
"truncated": {
"default": false,
"title": "Truncated",
"type": "boolean"
},
"data_window": {
"anyOf": [
{
"$ref": "#/$defs/DataWindow"
},
{
"type": "null"
}
],
"default": null
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Error"
}
},
"required": [
"success"
],
"title": "GetAllTopArtistsResult",
"type": "object"
} # get_all_top_tracks read
List up to 1000 top tracks across all stats pages and return the full ranked track dataset.
Full description
Get all top tracks by paginating through the full stats list.
Automatically handles pagination, returning up to 1000 tracks.
For most users this returns the complete dataset.
Args:
range: Time window. Defaults to 'all_time'. Note that LB-aligned
tokens like 'week'/'month'/'year' mean the PREVIOUS FULL
calendar period, not the last N days.
username: ListenBrainz username (defaults to authenticated user) | Parameter | Type | Required | Description |
|---|---|---|---|
range | "week" | "month" | "quarter" | "half_yearly" | "year" | "all_time" | "this_week" | "this_month" | "this_year" | No | Time window for the full top-tracks ranking. Previous full calendar period (LB-aligned): 'week', 'month', 'quarter', 'half_yearly', 'year'. Current period (period start to now): 'this_week', 'this_month', 'this_year'. Open-ended: 'all_time'. All period boundaries are UTC-aligned. For users in non-UTC zones the boundary instant differs from local midnight — e.g. 'this_week' for a Pacific user starts at Sunday 17:00 local (Monday 00:00 UTC). This is rarely worth correcting for ranking endpoints (a few hours' worth of listens won't reorder a top-N), but matters for tools that bin per-listen. get_daily_activity exposes a `timezone` parameter that actually shifts bucket alignment to the user's zone — pass it when known. get_listening_activity accepts the same param for consistency but its time-series bucket boundaries remain UTC-aligned regardless (rebinning is deferred work — use get_recent_listens + client-side bucketing if exact local boundaries matter). Note: 'week'/'month'/'year' mean the previous FULL calendar period, not the last N days. Default: "all_time" |
username | string | null | No | ListenBrainz username to query. Defaults to the authenticated user. |
Parameter schema (JSON)
{
"properties": {
"range": {
"default": "all_time",
"description": "Time window for the full top-tracks ranking. Previous full calendar period (LB-aligned): 'week', 'month', 'quarter', 'half_yearly', 'year'. Current period (period start to now): 'this_week', 'this_month', 'this_year'. Open-ended: 'all_time'. All period boundaries are UTC-aligned. For users in non-UTC zones the boundary instant differs from local midnight — e.g. 'this_week' for a Pacific user starts at Sunday 17:00 local (Monday 00:00 UTC). This is rarely worth correcting for ranking endpoints (a few hours' worth of listens won't reorder a top-N), but matters for tools that bin per-listen. get_daily_activity exposes a `timezone` parameter that actually shifts bucket alignment to the user's zone — pass it when known. get_listening_activity accepts the same param for consistency but its time-series bucket boundaries remain UTC-aligned regardless (rebinning is deferred work — use get_recent_listens + client-side bucketing if exact local boundaries matter). Note: 'week'/'month'/'year' mean the previous FULL calendar period, not the last N days.",
"enum": [
"week",
"month",
"quarter",
"half_yearly",
"year",
"all_time",
"this_week",
"this_month",
"this_year"
],
"title": "Range",
"type": "string"
},
"username": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "ListenBrainz username to query. Defaults to the authenticated user.",
"title": "Username"
}
},
"title": "GetAllTopTracksParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"DataWindow": {
"description": "Surfaces coverage information when a stats response may not\nfully cover the requested time range — most commonly when the\nresponse is empty and the LLM agent should retry with a different\n`range` token.\n\nPopulated on stats responses (top_artists/albums/tracks,\nlisten_count, daily/listening activity, etc.) by\n`_build_data_window` per toolforest_tools#1294. Currently\npopulated only for empty-response cases; see issue body for the\n\"obviously truncated but non-empty\" follow-up case (rolling-token\ncache wall) which is intentionally deferred.",
"properties": {
"requested_range": {
"description": "The `range` token the caller passed in.",
"title": "Requested Range",
"type": "string"
},
"note": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Human-readable explanation of why the response may not fully cover the requested range. LLM agents should relay this verbatim and consider the suggested retry. Common case: empty response because the user asked for `year` (previous full calendar year) but is a new account with no listens in that window — retry with `this_year`.",
"title": "Note"
}
},
"required": [
"requested_range"
],
"title": "DataWindow",
"type": "object"
},
"TopEntity": {
"description": "Shared model for top artists, albums, tracks.\n\n`mbid` holds the primary MusicBrainz identifier for the entity:\n - top artists → artist MBID\n - top albums → release MBID\n - top tracks → recording MBID\n\n`mbid` is `null` whenever the underlying listen records did not\ncarry a MusicBrainz ID. This is common in practice: scrobbles from\nSpotify and other connected services often arrive without MBIDs,\nand ListenBrainz does not back-fill them on the stats path. If you\nneed MBIDs to feed into MBID-keyed tools (get_similar_artists,\nadd_tracks_to_playlist, etc.), resolve names via\nlistenbrainz-artist_lookup or listenbrainz-metadata_lookup first\nrather than treating null `mbid` as a hard failure.\n\n`artist_mbid` is a convenience secondary field populated for top\nalbums and top tracks (where the primary `mbid` is the release or\nrecording MBID) so callers can resolve the artist without a second\ncall. For top artists it's the same value as `mbid`.",
"properties": {
"name": {
"title": "Name",
"type": "string"
},
"listen_count": {
"title": "Listen Count",
"type": "integer"
},
"artist_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Artist Name"
},
"release_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Release Name"
},
"mbid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Mbid"
},
"artist_mbid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Artist Mbid"
}
},
"required": [
"name",
"listen_count"
],
"title": "TopEntity",
"type": "object"
}
},
"properties": {
"success": {
"title": "Success",
"type": "boolean"
},
"tracks": {
"anyOf": [
{
"items": {
"$ref": "#/$defs/TopEntity"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Tracks"
},
"total_count": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Total Count"
},
"truncated": {
"default": false,
"title": "Truncated",
"type": "boolean"
},
"data_window": {
"anyOf": [
{
"$ref": "#/$defs/DataWindow"
},
{
"type": "null"
}
],
"default": null
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Error"
}
},
"required": [
"success"
],
"title": "GetAllTopTracksResult",
"type": "object"
} # get_connected_services read
List connected external music services like Spotify or Apple Music for export and sync checks.
Full description
Check which external services (Spotify, Apple Music, etc.) are connected. Useful before calling export_playlist to verify the target service is connected.
No parameters.
Parameter schema (JSON)
{
"properties": {},
"title": "GetConnectedServicesParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"ConnectedService": {
"properties": {
"service": {
"title": "Service",
"type": "string"
},
"connected": {
"title": "Connected",
"type": "boolean"
}
},
"required": [
"service",
"connected"
],
"title": "ConnectedService",
"type": "object"
}
},
"properties": {
"success": {
"title": "Success",
"type": "boolean"
},
"services": {
"anyOf": [
{
"items": {
"$ref": "#/$defs/ConnectedService"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Services"
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Error"
}
},
"required": [
"success"
],
"title": "GetConnectedServicesResult",
"type": "object"
} # get_daily_activity read
Get listening activity by weekday and hour, returning a habit heatmap-style breakdown.
Full description
Get the user's listening activity broken down by day of week and hour.
Shows when the user listens to music most — useful for understanding habits.
Args:
range: Time window. Defaults to 'this_month' (LB-native so it works
for any user). Note that LB-aligned tokens like 'week'/'month'/
'year' mean the PREVIOUS FULL calendar period, not the last N
days. | Parameter | Type | Required | Description |
|---|---|---|---|
range | "week" | "month" | "quarter" | "half_yearly" | "year" | "all_time" | "this_week" | "this_month" | "this_year" | No | Time window for the day-of-week x hour heatmap. Previous full calendar period (LB-aligned): 'week', 'month', 'quarter', 'half_yearly', 'year'. Current period (period start to now): 'this_week', 'this_month', 'this_year'. Open-ended: 'all_time'. All period boundaries are UTC-aligned. For users in non-UTC zones the boundary instant differs from local midnight — e.g. 'this_week' for a Pacific user starts at Sunday 17:00 local (Monday 00:00 UTC). This is rarely worth correcting for ranking endpoints (a few hours' worth of listens won't reorder a top-N), but matters for tools that bin per-listen. get_daily_activity exposes a `timezone` parameter that actually shifts bucket alignment to the user's zone — pass it when known. get_listening_activity accepts the same param for consistency but its time-series bucket boundaries remain UTC-aligned regardless (rebinning is deferred work — use get_recent_listens + client-side bucketing if exact local boundaries matter). Note: 'week'/'month'/'year' mean the previous FULL calendar period, not the last N days — use 'this_week'/'this_month'/'this_year' for the open-ended current period. Default: "this_month" |
timezone | string | No | IANA timezone name (e.g. 'America/Los_Angeles', 'Europe/London', 'Asia/Tokyo'). Weekday and hour bins are computed in this zone so a Pacific user's 10pm listen is bucketed as (whatever day, hour=22) — not 06:00 UTC on the next day. Default 'UTC' preserves the LB API's native bucketing; prefer passing the user's zone when you know it, since the natural interpretation of 'day of week' and 'hour of day' is always local time.
DST approximation: the shift uses the zone's UTC offset at query time, applied uniformly to the whole histogram. For short windows ('this_week', 'this_month') this is exact. For longer windows that span a DST transition ('year', 'all_time'), listens in the opposing DST period will be bucketed ±1 hour off — acceptable for heatmap interpretation, not for precise time analysis.
Half-hour zones (India UTC+5:30, Iran UTC+3:30, Newfoundland UTC-3:30, etc.) are truncated toward zero to whole hours, so results are 30 minutes off in those zones. Use UTC if exact alignment matters. Default: "UTC" |
username | string | null | No | ListenBrainz username to query. Defaults to the authenticated user. |
Parameter schema (JSON)
{
"properties": {
"range": {
"default": "this_month",
"description": "Time window for the day-of-week x hour heatmap. Previous full calendar period (LB-aligned): 'week', 'month', 'quarter', 'half_yearly', 'year'. Current period (period start to now): 'this_week', 'this_month', 'this_year'. Open-ended: 'all_time'. All period boundaries are UTC-aligned. For users in non-UTC zones the boundary instant differs from local midnight — e.g. 'this_week' for a Pacific user starts at Sunday 17:00 local (Monday 00:00 UTC). This is rarely worth correcting for ranking endpoints (a few hours' worth of listens won't reorder a top-N), but matters for tools that bin per-listen. get_daily_activity exposes a `timezone` parameter that actually shifts bucket alignment to the user's zone — pass it when known. get_listening_activity accepts the same param for consistency but its time-series bucket boundaries remain UTC-aligned regardless (rebinning is deferred work — use get_recent_listens + client-side bucketing if exact local boundaries matter). Note: 'week'/'month'/'year' mean the previous FULL calendar period, not the last N days — use 'this_week'/'this_month'/'this_year' for the open-ended current period.",
"enum": [
"week",
"month",
"quarter",
"half_yearly",
"year",
"all_time",
"this_week",
"this_month",
"this_year"
],
"title": "Range",
"type": "string"
},
"timezone": {
"default": "UTC",
"description": "IANA timezone name (e.g. 'America/Los_Angeles', 'Europe/London', 'Asia/Tokyo'). Weekday and hour bins are computed in this zone so a Pacific user's 10pm listen is bucketed as (whatever day, hour=22) — not 06:00 UTC on the next day. Default 'UTC' preserves the LB API's native bucketing; prefer passing the user's zone when you know it, since the natural interpretation of 'day of week' and 'hour of day' is always local time.\n\nDST approximation: the shift uses the zone's UTC offset at query time, applied uniformly to the whole histogram. For short windows ('this_week', 'this_month') this is exact. For longer windows that span a DST transition ('year', 'all_time'), listens in the opposing DST period will be bucketed ±1 hour off — acceptable for heatmap interpretation, not for precise time analysis.\n\nHalf-hour zones (India UTC+5:30, Iran UTC+3:30, Newfoundland UTC-3:30, etc.) are truncated toward zero to whole hours, so results are 30 minutes off in those zones. Use UTC if exact alignment matters.",
"title": "Timezone",
"type": "string"
},
"username": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "ListenBrainz username to query. Defaults to the authenticated user.",
"title": "Username"
}
},
"title": "GetDailyActivityParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"DailyActivityDay": {
"properties": {
"day": {
"title": "Day",
"type": "string"
},
"hours": {
"items": {
"$ref": "#/$defs/HourActivity"
},
"title": "Hours",
"type": "array"
}
},
"required": [
"day",
"hours"
],
"title": "DailyActivityDay",
"type": "object"
},
"DataWindow": {
"description": "Surfaces coverage information when a stats response may not\nfully cover the requested time range — most commonly when the\nresponse is empty and the LLM agent should retry with a different\n`range` token.\n\nPopulated on stats responses (top_artists/albums/tracks,\nlisten_count, daily/listening activity, etc.) by\n`_build_data_window` per toolforest_tools#1294. Currently\npopulated only for empty-response cases; see issue body for the\n\"obviously truncated but non-empty\" follow-up case (rolling-token\ncache wall) which is intentionally deferred.",
"properties": {
"requested_range": {
"description": "The `range` token the caller passed in.",
"title": "Requested Range",
"type": "string"
},
"note": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Human-readable explanation of why the response may not fully cover the requested range. LLM agents should relay this verbatim and consider the suggested retry. Common case: empty response because the user asked for `year` (previous full calendar year) but is a new account with no listens in that window — retry with `this_year`.",
"title": "Note"
}
},
"required": [
"requested_range"
],
"title": "DataWindow",
"type": "object"
},
"HourActivity": {
"properties": {
"hour": {
"title": "Hour",
"type": "integer"
},
"listen_count": {
"title": "Listen Count",
"type": "integer"
}
},
"required": [
"hour",
"listen_count"
],
"title": "HourActivity",
"type": "object"
}
},
"properties": {
"success": {
"title": "Success",
"type": "boolean"
},
"days": {
"anyOf": [
{
"items": {
"$ref": "#/$defs/DailyActivityDay"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Days"
},
"timezone": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "The IANA timezone the weekday and hour bins are computed in. Echoes back the `timezone` input parameter so callers can't silently misread 'Monday 6am' as Pacific when it's actually UTC.",
"title": "Timezone"
},
"data_window": {
"anyOf": [
{
"$ref": "#/$defs/DataWindow"
},
{
"type": "null"
}
],
"default": null
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Error"
}
},
"required": [
"success"
],
"title": "GetDailyActivityResult",
"type": "object"
} # get_listen_count read
Get the total number of listens for the user.
| Parameter | Type | Required | Description |
|---|---|---|---|
username | string | null | No | ListenBrainz username to query. Defaults to the authenticated user. |
Parameter schema (JSON)
{
"properties": {
"username": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "ListenBrainz username to query. Defaults to the authenticated user.",
"title": "Username"
}
},
"title": "GetListenCountParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"DataWindow": {
"description": "Surfaces coverage information when a stats response may not\nfully cover the requested time range — most commonly when the\nresponse is empty and the LLM agent should retry with a different\n`range` token.\n\nPopulated on stats responses (top_artists/albums/tracks,\nlisten_count, daily/listening activity, etc.) by\n`_build_data_window` per toolforest_tools#1294. Currently\npopulated only for empty-response cases; see issue body for the\n\"obviously truncated but non-empty\" follow-up case (rolling-token\ncache wall) which is intentionally deferred.",
"properties": {
"requested_range": {
"description": "The `range` token the caller passed in.",
"title": "Requested Range",
"type": "string"
},
"note": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Human-readable explanation of why the response may not fully cover the requested range. LLM agents should relay this verbatim and consider the suggested retry. Common case: empty response because the user asked for `year` (previous full calendar year) but is a new account with no listens in that window — retry with `this_year`.",
"title": "Note"
}
},
"required": [
"requested_range"
],
"title": "DataWindow",
"type": "object"
}
},
"properties": {
"success": {
"title": "Success",
"type": "boolean"
},
"count": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Count"
},
"data_window": {
"anyOf": [
{
"$ref": "#/$defs/DataWindow"
},
{
"type": "null"
}
],
"default": null
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Error"
}
},
"required": [
"success"
],
"title": "GetListenCountResult",
"type": "object"
} # get_listening_activity read
Get listen counts over time, returning a day, week, or month time-series of activity.
Full description
Get the user's listening activity over time (listen counts per day/week/month).
Returns a time series of listen counts, useful for tracking listening habits
and trends. Each entry has a time range label (e.g., '2026-04-01') and count.
Args:
range: Time window. Defaults to 'this_month' (LB-native so it works
for any user). Note that LB-aligned tokens like 'week'/'month'/
'year' mean the PREVIOUS FULL calendar period, not the last N
days. | Parameter | Type | Required | Description |
|---|---|---|---|
range | "week" | "month" | "quarter" | "half_yearly" | "year" | "all_time" | "this_week" | "this_month" | "this_year" | No | Time window for the listen-count time series. Previous full calendar period (LB-aligned): 'week', 'month', 'quarter', 'half_yearly', 'year'. Current period (period start to now): 'this_week', 'this_month', 'this_year'. Open-ended: 'all_time'. All period boundaries are UTC-aligned. For users in non-UTC zones the boundary instant differs from local midnight — e.g. 'this_week' for a Pacific user starts at Sunday 17:00 local (Monday 00:00 UTC). This is rarely worth correcting for ranking endpoints (a few hours' worth of listens won't reorder a top-N), but matters for tools that bin per-listen. get_daily_activity exposes a `timezone` parameter that actually shifts bucket alignment to the user's zone — pass it when known. get_listening_activity accepts the same param for consistency but its time-series bucket boundaries remain UTC-aligned regardless (rebinning is deferred work — use get_recent_listens + client-side bucketing if exact local boundaries matter). Note: 'week'/'month'/'year' mean the previous FULL calendar period, not the last N days — use 'this_week'/'this_month'/'this_year' for the open-ended current period. Default: "this_month" |
timezone | string | No | IANA timezone name. Accepted for consistency with get_daily_activity and echoed back in the response, BUT the time-series bucket boundaries in this tool remain UTC-aligned — rebinning to local-day/week/month boundaries requires re-aggregating from raw listens and is deferred work. Use get_recent_listens + client-side bucketing if exact local-day boundaries matter. Default 'UTC'. Default: "UTC" |
username | string | null | No | ListenBrainz username to query. Defaults to the authenticated user. |
Parameter schema (JSON)
{
"properties": {
"range": {
"default": "this_month",
"description": "Time window for the listen-count time series. Previous full calendar period (LB-aligned): 'week', 'month', 'quarter', 'half_yearly', 'year'. Current period (period start to now): 'this_week', 'this_month', 'this_year'. Open-ended: 'all_time'. All period boundaries are UTC-aligned. For users in non-UTC zones the boundary instant differs from local midnight — e.g. 'this_week' for a Pacific user starts at Sunday 17:00 local (Monday 00:00 UTC). This is rarely worth correcting for ranking endpoints (a few hours' worth of listens won't reorder a top-N), but matters for tools that bin per-listen. get_daily_activity exposes a `timezone` parameter that actually shifts bucket alignment to the user's zone — pass it when known. get_listening_activity accepts the same param for consistency but its time-series bucket boundaries remain UTC-aligned regardless (rebinning is deferred work — use get_recent_listens + client-side bucketing if exact local boundaries matter). Note: 'week'/'month'/'year' mean the previous FULL calendar period, not the last N days — use 'this_week'/'this_month'/'this_year' for the open-ended current period.",
"enum": [
"week",
"month",
"quarter",
"half_yearly",
"year",
"all_time",
"this_week",
"this_month",
"this_year"
],
"title": "Range",
"type": "string"
},
"timezone": {
"default": "UTC",
"description": "IANA timezone name. Accepted for consistency with get_daily_activity and echoed back in the response, BUT the time-series bucket boundaries in this tool remain UTC-aligned — rebinning to local-day/week/month boundaries requires re-aggregating from raw listens and is deferred work. Use get_recent_listens + client-side bucketing if exact local-day boundaries matter. Default 'UTC'.",
"title": "Timezone",
"type": "string"
},
"username": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "ListenBrainz username to query. Defaults to the authenticated user.",
"title": "Username"
}
},
"title": "GetListeningActivityParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"DataWindow": {
"description": "Surfaces coverage information when a stats response may not\nfully cover the requested time range — most commonly when the\nresponse is empty and the LLM agent should retry with a different\n`range` token.\n\nPopulated on stats responses (top_artists/albums/tracks,\nlisten_count, daily/listening activity, etc.) by\n`_build_data_window` per toolforest_tools#1294. Currently\npopulated only for empty-response cases; see issue body for the\n\"obviously truncated but non-empty\" follow-up case (rolling-token\ncache wall) which is intentionally deferred.",
"properties": {
"requested_range": {
"description": "The `range` token the caller passed in.",
"title": "Requested Range",
"type": "string"
},
"note": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Human-readable explanation of why the response may not fully cover the requested range. LLM agents should relay this verbatim and consider the suggested retry. Common case: empty response because the user asked for `year` (previous full calendar year) but is a new account with no listens in that window — retry with `this_year`.",
"title": "Note"
}
},
"required": [
"requested_range"
],
"title": "DataWindow",
"type": "object"
},
"ListeningActivityEntry": {
"properties": {
"from_ts": {
"title": "From Ts",
"type": "integer"
},
"to_ts": {
"title": "To Ts",
"type": "integer"
},
"time_range": {
"title": "Time Range",
"type": "string"
},
"listen_count": {
"title": "Listen Count",
"type": "integer"
}
},
"required": [
"from_ts",
"to_ts",
"time_range",
"listen_count"
],
"title": "ListeningActivityEntry",
"type": "object"
}
},
"properties": {
"success": {
"title": "Success",
"type": "boolean"
},
"activity": {
"anyOf": [
{
"items": {
"$ref": "#/$defs/ListeningActivityEntry"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Activity"
},
"timezone": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Echoes back the `timezone` input parameter. NOTE: bucket boundaries (from_ts/to_ts, time_range label) in this response remain UTC-aligned regardless of this value — rebinning to local day/week/month boundaries requires re-aggregating raw listens and is deferred work. Do not interpret this field as meaning buckets are drawn in the specified zone. For exact local-boundary bucketing, pull raw listens via get_recent_listens / get_all_listens and aggregate client-side.",
"title": "Timezone"
},
"data_window": {
"anyOf": [
{
"$ref": "#/$defs/DataWindow"
},
{
"type": "null"
}
],
"default": null
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Error"
}
},
"required": [
"success"
],
"title": "GetListeningActivityResult",
"type": "object"
} # get_playing_now read
Get the track the user is currently listening to, if any.
| Parameter | Type | Required | Description |
|---|---|---|---|
username | string | null | No | ListenBrainz username to query. Defaults to the authenticated user. |
Parameter schema (JSON)
{
"properties": {
"username": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "ListenBrainz username to query. Defaults to the authenticated user.",
"title": "Username"
}
},
"title": "GetPlayingNowParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"Listen": {
"properties": {
"track_name": {
"title": "Track Name",
"type": "string"
},
"artist_name": {
"title": "Artist Name",
"type": "string"
},
"release_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Release Name"
},
"listened_at": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Listened At"
},
"recording_mbid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Recording Mbid"
},
"artist_mbids": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Artist Mbids"
},
"genres": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Genres"
},
"release_group_type": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Release Group Type"
}
},
"required": [
"track_name",
"artist_name"
],
"title": "Listen",
"type": "object"
}
},
"properties": {
"success": {
"title": "Success",
"type": "boolean"
},
"listen": {
"anyOf": [
{
"$ref": "#/$defs/Listen"
},
{
"type": "null"
}
],
"default": null
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Error"
}
},
"required": [
"success"
],
"title": "GetPlayingNowResult",
"type": "object"
} # get_playlist read
Get a specific playlist and its tracks from ListenBrainz, or from a connected Spotify, Apple Music, or SoundCloud account via the source parameter.
Full description
Get a specific playlist and its tracks.
Args:
source: Playlist source (listenbrainz, spotify, apple_music, soundcloud)
playlist_id: Playlist ID (MBID for listenbrainz, platform ID for others) | Parameter | Type | Required | Description |
|---|---|---|---|
source | string | No | Playlist source: listenbrainz, spotify, apple_music, soundcloud Default: "listenbrainz" |
playlist_id | string | null | No | Playlist ID (MBID for listenbrainz, platform ID for others). Provide this OR `playlist_mbid`. |
playlist_mbid | string | null | No | Alias for `playlist_id` matching the field name on playlist-list responses. Provide this OR `playlist_id`. |
Parameter schema (JSON)
{
"properties": {
"source": {
"default": "listenbrainz",
"description": "Playlist source: listenbrainz, spotify, apple_music, soundcloud",
"title": "Source",
"type": "string"
},
"playlist_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Playlist ID (MBID for listenbrainz, platform ID for others). Provide this OR `playlist_mbid`.",
"title": "Playlist Id"
},
"playlist_mbid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Alias for `playlist_id` matching the field name on playlist-list responses. Provide this OR `playlist_id`.",
"title": "Playlist Mbid"
}
},
"title": "GetPlaylistParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"PlaylistDetail": {
"properties": {
"playlist_mbid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Playlist Mbid"
},
"playlist_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Playlist Id"
},
"source": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Source"
},
"title": {
"title": "Title",
"type": "string"
},
"creator": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Creator"
},
"description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Description"
},
"tracks": {
"anyOf": [
{
"items": {
"$ref": "#/$defs/PlaylistTrack"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Tracks"
},
"track_count": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Track Count"
},
"created_at": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Created At"
},
"public": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"title": "Public"
},
"playlist_url": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Shareable web URL for the playlist. Derived for sources where the URL format is stable (listenbrainz, spotify). Spotify URLs are always constructable from the platform ID, but private Spotify playlists are only accessible to their owner. None for apple_music and soundcloud where the URL needs region/slug data that isn't always in the API response.",
"title": "Playlist Url"
}
},
"required": [
"title"
],
"title": "PlaylistDetail",
"type": "object"
},
"PlaylistTrack": {
"properties": {
"title": {
"title": "Title",
"type": "string"
},
"creator": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Creator"
},
"identifier": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Identifier"
},
"recording_mbid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Recording Mbid"
}
},
"required": [
"title"
],
"title": "PlaylistTrack",
"type": "object"
}
},
"properties": {
"success": {
"title": "Success",
"type": "boolean"
},
"playlist": {
"anyOf": [
{
"$ref": "#/$defs/PlaylistDetail"
},
{
"type": "null"
}
],
"default": null
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Error"
}
},
"required": [
"success"
],
"title": "GetPlaylistResult",
"type": "object"
} # get_playlists read
List a user's playlists from ListenBrainz, or from connected Spotify, Apple Music, or SoundCloud via the source parameter.
Full description
Get the user's playlists from ListenBrainz or an external service.
Args:
source: Playlist source (listenbrainz, spotify, apple_music, soundcloud)
count: Number of playlists (default 25, max 100)
offset: Offset for pagination | Parameter | Type | Required | Description |
|---|---|---|---|
source | string | No | Playlist source: listenbrainz, spotify, apple_music, soundcloud Default: "listenbrainz" |
count | integer | No | Number of playlists (max 100) Default: 25 |
offset | integer | No | Offset for pagination Default: 0 |
Parameter schema (JSON)
{
"properties": {
"source": {
"default": "listenbrainz",
"description": "Playlist source: listenbrainz, spotify, apple_music, soundcloud",
"title": "Source",
"type": "string"
},
"count": {
"default": 25,
"description": "Number of playlists (max 100)",
"maximum": 100,
"minimum": 1,
"title": "Count",
"type": "integer"
},
"offset": {
"default": 0,
"description": "Offset for pagination",
"minimum": 0,
"title": "Offset",
"type": "integer"
}
},
"title": "GetPlaylistsParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"PlaylistSummary": {
"properties": {
"playlist_mbid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Playlist Mbid"
},
"playlist_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Playlist Id"
},
"source": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Source"
},
"title": {
"title": "Title",
"type": "string"
},
"creator": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Creator"
},
"track_count": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Track Count"
},
"created_at": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Created At"
},
"description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Description"
},
"playlist_url": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Shareable web URL for the playlist. Derived for sources where the URL format is stable (listenbrainz, spotify). Spotify URLs are always constructable from the platform ID, but private Spotify playlists are only accessible to their owner. None for apple_music and soundcloud where the URL needs region/slug data that isn't always in the API response.",
"title": "Playlist Url"
}
},
"required": [
"title"
],
"title": "PlaylistSummary",
"type": "object"
}
},
"properties": {
"success": {
"title": "Success",
"type": "boolean"
},
"playlists": {
"anyOf": [
{
"items": {
"$ref": "#/$defs/PlaylistSummary"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Playlists"
},
"total_count": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Total Count"
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Error"
}
},
"required": [
"success"
],
"title": "GetPlaylistsResult",
"type": "object"
} # get_recent_listens read
Get the user's most recent listens with timestamps and track metadata.
Full description
Get the user's recent listening history.
Returns a list of recently played tracks with artist, track, and album info.
Timestamps (`listened_at`, `min_ts`, `max_ts`, the `oldest_listen_ts` /
`newest_listen_ts` cursors) are Unix epoch seconds — zone-agnostic
integers. When converting them to wall-clock dates/times for a user,
apply the user's local IANA timezone explicitly. The natural
interpretation of "Saturday 4am" is local time, but a Pacific user's
Friday 9pm listen is Saturday 04:00 UTC — silent off-by-one risk if
the conversion is implicit.
Args:
count: Number of listens to return (default 25, max 100)
max_ts: Return listens before this Unix timestamp (UTC seconds)
min_ts: Return listens after this Unix timestamp (UTC seconds) | Parameter | Type | Required | Description |
|---|---|---|---|
count | integer | No | Number of listens to return (max 100) Default: 25 |
max_ts | integer | null | No | Return listens before this Unix timestamp |
min_ts | integer | null | No | Return listens after this Unix timestamp |
username | string | null | No | ListenBrainz username to query. Defaults to the authenticated user. |
Parameter schema (JSON)
{
"properties": {
"count": {
"default": 25,
"description": "Number of listens to return (max 100)",
"maximum": 100,
"minimum": 1,
"title": "Count",
"type": "integer"
},
"max_ts": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Return listens before this Unix timestamp",
"title": "Max Ts"
},
"min_ts": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Return listens after this Unix timestamp",
"title": "Min Ts"
},
"username": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "ListenBrainz username to query. Defaults to the authenticated user.",
"title": "Username"
}
},
"title": "GetRecentListensParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"Listen": {
"properties": {
"track_name": {
"title": "Track Name",
"type": "string"
},
"artist_name": {
"title": "Artist Name",
"type": "string"
},
"release_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Release Name"
},
"listened_at": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Listened At"
},
"recording_mbid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Recording Mbid"
},
"artist_mbids": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Artist Mbids"
},
"genres": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Genres"
},
"release_group_type": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Release Group Type"
}
},
"required": [
"track_name",
"artist_name"
],
"title": "Listen",
"type": "object"
}
},
"properties": {
"success": {
"title": "Success",
"type": "boolean"
},
"listens": {
"anyOf": [
{
"items": {
"$ref": "#/$defs/Listen"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Listens"
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Error"
}
},
"required": [
"success"
],
"title": "GetRecentListensResult",
"type": "object"
} # get_recommendation_playlists read
List auto-generated recommendation playlists like Weekly Jams and Weekly Exploration.
Full description
Get playlists generated by ListenBrainz's recommendation engine for the user. These are auto-generated playlists like 'Weekly Jams' and 'Weekly Exploration'.
| Parameter | Type | Required | Description |
|---|---|---|---|
username | string | null | No | ListenBrainz username to query. Defaults to the authenticated user. |
Parameter schema (JSON)
{
"properties": {
"username": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "ListenBrainz username to query. Defaults to the authenticated user.",
"title": "Username"
}
},
"title": "GetRecommendationPlaylistsParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"PlaylistSummary": {
"properties": {
"playlist_mbid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Playlist Mbid"
},
"playlist_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Playlist Id"
},
"source": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Source"
},
"title": {
"title": "Title",
"type": "string"
},
"creator": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Creator"
},
"track_count": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Track Count"
},
"created_at": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Created At"
},
"description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Description"
},
"playlist_url": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Shareable web URL for the playlist. Derived for sources where the URL format is stable (listenbrainz, spotify). Spotify URLs are always constructable from the platform ID, but private Spotify playlists are only accessible to their owner. None for apple_music and soundcloud where the URL needs region/slug data that isn't always in the API response.",
"title": "Playlist Url"
}
},
"required": [
"title"
],
"title": "PlaylistSummary",
"type": "object"
}
},
"properties": {
"success": {
"title": "Success",
"type": "boolean"
},
"playlists": {
"anyOf": [
{
"items": {
"$ref": "#/$defs/PlaylistSummary"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Playlists"
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Error"
}
},
"required": [
"success"
],
"title": "GetRecommendationPlaylistsResult",
"type": "object"
} # get_recommendations read
Get personalized track recommendations based on the user's listening history.
Full description
Get personalized track recommendations based on listening history.
Args:
count: Number of recommendations (default 25, max 100)
offset: Offset for pagination | Parameter | Type | Required | Description |
|---|---|---|---|
count | integer | No | Number of recommendations Default: 25 |
offset | integer | No | Offset for pagination Default: 0 |
username | string | null | No | ListenBrainz username to query. Defaults to the authenticated user. |
Parameter schema (JSON)
{
"properties": {
"count": {
"default": 25,
"description": "Number of recommendations",
"maximum": 100,
"minimum": 1,
"title": "Count",
"type": "integer"
},
"offset": {
"default": 0,
"description": "Offset for pagination",
"minimum": 0,
"title": "Offset",
"type": "integer"
},
"username": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "ListenBrainz username to query. Defaults to the authenticated user.",
"title": "Username"
}
},
"title": "GetRecommendationsParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"Listen": {
"properties": {
"track_name": {
"title": "Track Name",
"type": "string"
},
"artist_name": {
"title": "Artist Name",
"type": "string"
},
"release_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Release Name"
},
"listened_at": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Listened At"
},
"recording_mbid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Recording Mbid"
},
"artist_mbids": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Artist Mbids"
},
"genres": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Genres"
},
"release_group_type": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Release Group Type"
}
},
"required": [
"track_name",
"artist_name"
],
"title": "Listen",
"type": "object"
}
},
"properties": {
"success": {
"title": "Success",
"type": "boolean"
},
"tracks": {
"anyOf": [
{
"items": {
"$ref": "#/$defs/Listen"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Tracks"
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Error"
}
},
"required": [
"success"
],
"title": "GetRecommendationsResult",
"type": "object"
} # get_similar_artists read
Get artists similar to a given artist and return similarity scores using ListenBrainz data.
Full description
Get artists similar to a given artist using ListenBrainz data.
Requires a MusicBrainz artist ID (MBID). Returns similar artists with
similarity scores. Use MusicBrainz search to find an artist's MBID first.
Args:
artist_mbid: MusicBrainz artist ID (UUID)
artist_name: Artist name (optional, for context in the response) | Parameter | Type | Required | Description |
|---|---|---|---|
artist_mbid | string | Yes | MusicBrainz artist ID (UUID) |
artist_name | string | null | No | Artist name (for context in the response, not used in the query) |
Parameter schema (JSON)
{
"properties": {
"artist_mbid": {
"description": "MusicBrainz artist ID (UUID)",
"title": "Artist Mbid",
"type": "string"
},
"artist_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Artist name (for context in the response, not used in the query)",
"title": "Artist Name"
}
},
"required": [
"artist_mbid"
],
"title": "GetSimilarArtistsParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"SimilarArtist": {
"properties": {
"artist_mbid": {
"title": "Artist Mbid",
"type": "string"
},
"name": {
"title": "Name",
"type": "string"
},
"score": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Score"
},
"reference_mbid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Reference Mbid"
}
},
"required": [
"artist_mbid",
"name"
],
"title": "SimilarArtist",
"type": "object"
}
},
"properties": {
"success": {
"title": "Success",
"type": "boolean"
},
"artists": {
"anyOf": [
{
"items": {
"$ref": "#/$defs/SimilarArtist"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Artists"
},
"query_artist_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Query Artist Name"
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Error"
}
},
"required": [
"success"
],
"title": "GetSimilarArtistsResult",
"type": "object"
} # get_similar_users read
Get ListenBrainz users with listening taste similar to the given user.
Full description
Get users with similar listening taste.
| Parameter | Type | Required | Description |
|---|---|---|---|
username | string | null | No | ListenBrainz username to query. Defaults to the authenticated user. |
Parameter schema (JSON)
{
"properties": {
"username": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "ListenBrainz username to query. Defaults to the authenticated user.",
"title": "Username"
}
},
"title": "GetSimilarUsersParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"SimilarUser": {
"properties": {
"user_name": {
"title": "User Name",
"type": "string"
},
"similarity": {
"title": "Similarity",
"type": "number"
}
},
"required": [
"user_name",
"similarity"
],
"title": "SimilarUser",
"type": "object"
}
},
"properties": {
"success": {
"title": "Success",
"type": "boolean"
},
"users": {
"anyOf": [
{
"items": {
"$ref": "#/$defs/SimilarUser"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Users"
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Error"
}
},
"required": [
"success"
],
"title": "GetSimilarUsersResult",
"type": "object"
} # get_top_albums read
Get the user's most-listened albums for a given time range.
Full description
Get the user's most-listened albums/releases for a given time range.
Args:
range: Time window. Defaults to 'month' (previous full calendar
month). Note that LB-aligned tokens like 'week'/'month'/'year'
mean the PREVIOUS FULL calendar period, not the last N days.
count: Number of results (default 10, max 100) | Parameter | Type | Required | Description |
|---|---|---|---|
range | "week" | "month" | "quarter" | "half_yearly" | "year" | "all_time" | "this_week" | "this_month" | "this_year" | No | Time window for the top-albums ranking. Previous full calendar period (LB-aligned): 'week', 'month', 'quarter', 'half_yearly', 'year'. Current period (period start to now): 'this_week', 'this_month', 'this_year'. Open-ended: 'all_time'. All period boundaries are UTC-aligned. For users in non-UTC zones the boundary instant differs from local midnight — e.g. 'this_week' for a Pacific user starts at Sunday 17:00 local (Monday 00:00 UTC). This is rarely worth correcting for ranking endpoints (a few hours' worth of listens won't reorder a top-N), but matters for tools that bin per-listen. get_daily_activity exposes a `timezone` parameter that actually shifts bucket alignment to the user's zone — pass it when known. get_listening_activity accepts the same param for consistency but its time-series bucket boundaries remain UTC-aligned regardless (rebinning is deferred work — use get_recent_listens + client-side bucketing if exact local boundaries matter). Note: 'week'/'month'/'year' mean the previous FULL calendar period, not the last N days. Default: "month" |
count | integer | No | Number of results (max 100) Default: 10 |
offset | integer | No | Offset for pagination Default: 0 |
username | string | null | No | ListenBrainz username to query. Defaults to the authenticated user. |
Parameter schema (JSON)
{
"properties": {
"range": {
"default": "month",
"description": "Time window for the top-albums ranking. Previous full calendar period (LB-aligned): 'week', 'month', 'quarter', 'half_yearly', 'year'. Current period (period start to now): 'this_week', 'this_month', 'this_year'. Open-ended: 'all_time'. All period boundaries are UTC-aligned. For users in non-UTC zones the boundary instant differs from local midnight — e.g. 'this_week' for a Pacific user starts at Sunday 17:00 local (Monday 00:00 UTC). This is rarely worth correcting for ranking endpoints (a few hours' worth of listens won't reorder a top-N), but matters for tools that bin per-listen. get_daily_activity exposes a `timezone` parameter that actually shifts bucket alignment to the user's zone — pass it when known. get_listening_activity accepts the same param for consistency but its time-series bucket boundaries remain UTC-aligned regardless (rebinning is deferred work — use get_recent_listens + client-side bucketing if exact local boundaries matter). Note: 'week'/'month'/'year' mean the previous FULL calendar period, not the last N days.",
"enum": [
"week",
"month",
"quarter",
"half_yearly",
"year",
"all_time",
"this_week",
"this_month",
"this_year"
],
"title": "Range",
"type": "string"
},
"count": {
"default": 10,
"description": "Number of results (max 100)",
"maximum": 100,
"minimum": 1,
"title": "Count",
"type": "integer"
},
"offset": {
"default": 0,
"description": "Offset for pagination",
"minimum": 0,
"title": "Offset",
"type": "integer"
},
"username": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "ListenBrainz username to query. Defaults to the authenticated user.",
"title": "Username"
}
},
"title": "GetTopAlbumsParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"DataWindow": {
"description": "Surfaces coverage information when a stats response may not\nfully cover the requested time range — most commonly when the\nresponse is empty and the LLM agent should retry with a different\n`range` token.\n\nPopulated on stats responses (top_artists/albums/tracks,\nlisten_count, daily/listening activity, etc.) by\n`_build_data_window` per toolforest_tools#1294. Currently\npopulated only for empty-response cases; see issue body for the\n\"obviously truncated but non-empty\" follow-up case (rolling-token\ncache wall) which is intentionally deferred.",
"properties": {
"requested_range": {
"description": "The `range` token the caller passed in.",
"title": "Requested Range",
"type": "string"
},
"note": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Human-readable explanation of why the response may not fully cover the requested range. LLM agents should relay this verbatim and consider the suggested retry. Common case: empty response because the user asked for `year` (previous full calendar year) but is a new account with no listens in that window — retry with `this_year`.",
"title": "Note"
}
},
"required": [
"requested_range"
],
"title": "DataWindow",
"type": "object"
},
"TopEntity": {
"description": "Shared model for top artists, albums, tracks.\n\n`mbid` holds the primary MusicBrainz identifier for the entity:\n - top artists → artist MBID\n - top albums → release MBID\n - top tracks → recording MBID\n\n`mbid` is `null` whenever the underlying listen records did not\ncarry a MusicBrainz ID. This is common in practice: scrobbles from\nSpotify and other connected services often arrive without MBIDs,\nand ListenBrainz does not back-fill them on the stats path. If you\nneed MBIDs to feed into MBID-keyed tools (get_similar_artists,\nadd_tracks_to_playlist, etc.), resolve names via\nlistenbrainz-artist_lookup or listenbrainz-metadata_lookup first\nrather than treating null `mbid` as a hard failure.\n\n`artist_mbid` is a convenience secondary field populated for top\nalbums and top tracks (where the primary `mbid` is the release or\nrecording MBID) so callers can resolve the artist without a second\ncall. For top artists it's the same value as `mbid`.",
"properties": {
"name": {
"title": "Name",
"type": "string"
},
"listen_count": {
"title": "Listen Count",
"type": "integer"
},
"artist_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Artist Name"
},
"release_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Release Name"
},
"mbid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Mbid"
},
"artist_mbid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Artist Mbid"
}
},
"required": [
"name",
"listen_count"
],
"title": "TopEntity",
"type": "object"
}
},
"properties": {
"success": {
"title": "Success",
"type": "boolean"
},
"albums": {
"anyOf": [
{
"items": {
"$ref": "#/$defs/TopEntity"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Albums"
},
"total_listen_count": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Total Listen Count"
},
"total_entity_count": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Total Entity Count"
},
"data_window": {
"anyOf": [
{
"$ref": "#/$defs/DataWindow"
},
{
"type": "null"
}
],
"default": null
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Error"
}
},
"required": [
"success"
],
"title": "GetTopAlbumsResult",
"type": "object"
} # get_top_artists read
Get the user's most-listened artists for a given time range.
Full description
Get the user's most-listened artists for a given time range.
Args:
range: Time window. Defaults to 'month' (previous full calendar
month). Note that LB-aligned tokens like 'week'/'month'/'year'
mean the PREVIOUS FULL calendar period, not the last N days.
count: Number of results (default 10, max 100) | Parameter | Type | Required | Description |
|---|---|---|---|
range | "week" | "month" | "quarter" | "half_yearly" | "year" | "all_time" | "this_week" | "this_month" | "this_year" | No | Time window for the top-artists ranking. Previous full calendar period (LB-aligned): 'week', 'month', 'quarter', 'half_yearly', 'year'. Current period (period start to now): 'this_week', 'this_month', 'this_year'. Open-ended: 'all_time'. All period boundaries are UTC-aligned. For users in non-UTC zones the boundary instant differs from local midnight — e.g. 'this_week' for a Pacific user starts at Sunday 17:00 local (Monday 00:00 UTC). This is rarely worth correcting for ranking endpoints (a few hours' worth of listens won't reorder a top-N), but matters for tools that bin per-listen. get_daily_activity exposes a `timezone` parameter that actually shifts bucket alignment to the user's zone — pass it when known. get_listening_activity accepts the same param for consistency but its time-series bucket boundaries remain UTC-aligned regardless (rebinning is deferred work — use get_recent_listens + client-side bucketing if exact local boundaries matter). Note: 'week'/'month'/'year' mean the previous FULL calendar period, not the last N days. Default: "month" |
count | integer | No | Number of results (max 100) Default: 10 |
offset | integer | No | Offset for pagination Default: 0 |
username | string | null | No | ListenBrainz username to query. Defaults to the authenticated user. |
Parameter schema (JSON)
{
"properties": {
"range": {
"default": "month",
"description": "Time window for the top-artists ranking. Previous full calendar period (LB-aligned): 'week', 'month', 'quarter', 'half_yearly', 'year'. Current period (period start to now): 'this_week', 'this_month', 'this_year'. Open-ended: 'all_time'. All period boundaries are UTC-aligned. For users in non-UTC zones the boundary instant differs from local midnight — e.g. 'this_week' for a Pacific user starts at Sunday 17:00 local (Monday 00:00 UTC). This is rarely worth correcting for ranking endpoints (a few hours' worth of listens won't reorder a top-N), but matters for tools that bin per-listen. get_daily_activity exposes a `timezone` parameter that actually shifts bucket alignment to the user's zone — pass it when known. get_listening_activity accepts the same param for consistency but its time-series bucket boundaries remain UTC-aligned regardless (rebinning is deferred work — use get_recent_listens + client-side bucketing if exact local boundaries matter). Note: 'week'/'month'/'year' mean the previous FULL calendar period, not the last N days.",
"enum": [
"week",
"month",
"quarter",
"half_yearly",
"year",
"all_time",
"this_week",
"this_month",
"this_year"
],
"title": "Range",
"type": "string"
},
"count": {
"default": 10,
"description": "Number of results (max 100)",
"maximum": 100,
"minimum": 1,
"title": "Count",
"type": "integer"
},
"offset": {
"default": 0,
"description": "Offset for pagination",
"minimum": 0,
"title": "Offset",
"type": "integer"
},
"username": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "ListenBrainz username to query. Defaults to the authenticated user.",
"title": "Username"
}
},
"title": "GetTopArtistsParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"DataWindow": {
"description": "Surfaces coverage information when a stats response may not\nfully cover the requested time range — most commonly when the\nresponse is empty and the LLM agent should retry with a different\n`range` token.\n\nPopulated on stats responses (top_artists/albums/tracks,\nlisten_count, daily/listening activity, etc.) by\n`_build_data_window` per toolforest_tools#1294. Currently\npopulated only for empty-response cases; see issue body for the\n\"obviously truncated but non-empty\" follow-up case (rolling-token\ncache wall) which is intentionally deferred.",
"properties": {
"requested_range": {
"description": "The `range` token the caller passed in.",
"title": "Requested Range",
"type": "string"
},
"note": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Human-readable explanation of why the response may not fully cover the requested range. LLM agents should relay this verbatim and consider the suggested retry. Common case: empty response because the user asked for `year` (previous full calendar year) but is a new account with no listens in that window — retry with `this_year`.",
"title": "Note"
}
},
"required": [
"requested_range"
],
"title": "DataWindow",
"type": "object"
},
"TopEntity": {
"description": "Shared model for top artists, albums, tracks.\n\n`mbid` holds the primary MusicBrainz identifier for the entity:\n - top artists → artist MBID\n - top albums → release MBID\n - top tracks → recording MBID\n\n`mbid` is `null` whenever the underlying listen records did not\ncarry a MusicBrainz ID. This is common in practice: scrobbles from\nSpotify and other connected services often arrive without MBIDs,\nand ListenBrainz does not back-fill them on the stats path. If you\nneed MBIDs to feed into MBID-keyed tools (get_similar_artists,\nadd_tracks_to_playlist, etc.), resolve names via\nlistenbrainz-artist_lookup or listenbrainz-metadata_lookup first\nrather than treating null `mbid` as a hard failure.\n\n`artist_mbid` is a convenience secondary field populated for top\nalbums and top tracks (where the primary `mbid` is the release or\nrecording MBID) so callers can resolve the artist without a second\ncall. For top artists it's the same value as `mbid`.",
"properties": {
"name": {
"title": "Name",
"type": "string"
},
"listen_count": {
"title": "Listen Count",
"type": "integer"
},
"artist_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Artist Name"
},
"release_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Release Name"
},
"mbid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Mbid"
},
"artist_mbid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Artist Mbid"
}
},
"required": [
"name",
"listen_count"
],
"title": "TopEntity",
"type": "object"
}
},
"properties": {
"success": {
"title": "Success",
"type": "boolean"
},
"artists": {
"anyOf": [
{
"items": {
"$ref": "#/$defs/TopEntity"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Artists"
},
"total_listen_count": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Total Listen Count"
},
"total_entity_count": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Total Entity Count"
},
"data_window": {
"anyOf": [
{
"$ref": "#/$defs/DataWindow"
},
{
"type": "null"
}
],
"default": null
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Error"
}
},
"required": [
"success"
],
"title": "GetTopArtistsResult",
"type": "object"
} # get_top_tracks read
Get the user's most-listened tracks for a given time range.
Full description
Get the user's most-listened tracks for a given time range.
Args:
range: Time window. Defaults to 'month' (previous full calendar
month). Note that LB-aligned tokens like 'week'/'month'/'year'
mean the PREVIOUS FULL calendar period, not the last N days.
count: Number of results (default 10, max 100) | Parameter | Type | Required | Description |
|---|---|---|---|
range | "week" | "month" | "quarter" | "half_yearly" | "year" | "all_time" | "this_week" | "this_month" | "this_year" | No | Time window for the top-tracks ranking. Previous full calendar period (LB-aligned): 'week', 'month', 'quarter', 'half_yearly', 'year'. Current period (period start to now): 'this_week', 'this_month', 'this_year'. Open-ended: 'all_time'. All period boundaries are UTC-aligned. For users in non-UTC zones the boundary instant differs from local midnight — e.g. 'this_week' for a Pacific user starts at Sunday 17:00 local (Monday 00:00 UTC). This is rarely worth correcting for ranking endpoints (a few hours' worth of listens won't reorder a top-N), but matters for tools that bin per-listen. get_daily_activity exposes a `timezone` parameter that actually shifts bucket alignment to the user's zone — pass it when known. get_listening_activity accepts the same param for consistency but its time-series bucket boundaries remain UTC-aligned regardless (rebinning is deferred work — use get_recent_listens + client-side bucketing if exact local boundaries matter). Note: 'week'/'month'/'year' mean the previous FULL calendar period, not the last N days. Default: "month" |
count | integer | No | Number of results (max 100) Default: 10 |
offset | integer | No | Offset for pagination Default: 0 |
username | string | null | No | ListenBrainz username to query. Defaults to the authenticated user. |
Parameter schema (JSON)
{
"properties": {
"range": {
"default": "month",
"description": "Time window for the top-tracks ranking. Previous full calendar period (LB-aligned): 'week', 'month', 'quarter', 'half_yearly', 'year'. Current period (period start to now): 'this_week', 'this_month', 'this_year'. Open-ended: 'all_time'. All period boundaries are UTC-aligned. For users in non-UTC zones the boundary instant differs from local midnight — e.g. 'this_week' for a Pacific user starts at Sunday 17:00 local (Monday 00:00 UTC). This is rarely worth correcting for ranking endpoints (a few hours' worth of listens won't reorder a top-N), but matters for tools that bin per-listen. get_daily_activity exposes a `timezone` parameter that actually shifts bucket alignment to the user's zone — pass it when known. get_listening_activity accepts the same param for consistency but its time-series bucket boundaries remain UTC-aligned regardless (rebinning is deferred work — use get_recent_listens + client-side bucketing if exact local boundaries matter). Note: 'week'/'month'/'year' mean the previous FULL calendar period, not the last N days.",
"enum": [
"week",
"month",
"quarter",
"half_yearly",
"year",
"all_time",
"this_week",
"this_month",
"this_year"
],
"title": "Range",
"type": "string"
},
"count": {
"default": 10,
"description": "Number of results (max 100)",
"maximum": 100,
"minimum": 1,
"title": "Count",
"type": "integer"
},
"offset": {
"default": 0,
"description": "Offset for pagination",
"minimum": 0,
"title": "Offset",
"type": "integer"
},
"username": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "ListenBrainz username to query. Defaults to the authenticated user.",
"title": "Username"
}
},
"title": "GetTopTracksParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"DataWindow": {
"description": "Surfaces coverage information when a stats response may not\nfully cover the requested time range — most commonly when the\nresponse is empty and the LLM agent should retry with a different\n`range` token.\n\nPopulated on stats responses (top_artists/albums/tracks,\nlisten_count, daily/listening activity, etc.) by\n`_build_data_window` per toolforest_tools#1294. Currently\npopulated only for empty-response cases; see issue body for the\n\"obviously truncated but non-empty\" follow-up case (rolling-token\ncache wall) which is intentionally deferred.",
"properties": {
"requested_range": {
"description": "The `range` token the caller passed in.",
"title": "Requested Range",
"type": "string"
},
"note": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Human-readable explanation of why the response may not fully cover the requested range. LLM agents should relay this verbatim and consider the suggested retry. Common case: empty response because the user asked for `year` (previous full calendar year) but is a new account with no listens in that window — retry with `this_year`.",
"title": "Note"
}
},
"required": [
"requested_range"
],
"title": "DataWindow",
"type": "object"
},
"TopEntity": {
"description": "Shared model for top artists, albums, tracks.\n\n`mbid` holds the primary MusicBrainz identifier for the entity:\n - top artists → artist MBID\n - top albums → release MBID\n - top tracks → recording MBID\n\n`mbid` is `null` whenever the underlying listen records did not\ncarry a MusicBrainz ID. This is common in practice: scrobbles from\nSpotify and other connected services often arrive without MBIDs,\nand ListenBrainz does not back-fill them on the stats path. If you\nneed MBIDs to feed into MBID-keyed tools (get_similar_artists,\nadd_tracks_to_playlist, etc.), resolve names via\nlistenbrainz-artist_lookup or listenbrainz-metadata_lookup first\nrather than treating null `mbid` as a hard failure.\n\n`artist_mbid` is a convenience secondary field populated for top\nalbums and top tracks (where the primary `mbid` is the release or\nrecording MBID) so callers can resolve the artist without a second\ncall. For top artists it's the same value as `mbid`.",
"properties": {
"name": {
"title": "Name",
"type": "string"
},
"listen_count": {
"title": "Listen Count",
"type": "integer"
},
"artist_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Artist Name"
},
"release_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Release Name"
},
"mbid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Mbid"
},
"artist_mbid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Artist Mbid"
}
},
"required": [
"name",
"listen_count"
],
"title": "TopEntity",
"type": "object"
}
},
"properties": {
"success": {
"title": "Success",
"type": "boolean"
},
"tracks": {
"anyOf": [
{
"items": {
"$ref": "#/$defs/TopEntity"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Tracks"
},
"total_listen_count": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Total Listen Count"
},
"total_entity_count": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Total Entity Count"
},
"data_window": {
"anyOf": [
{
"$ref": "#/$defs/DataWindow"
},
{
"type": "null"
}
],
"default": null
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Error"
}
},
"required": [
"success"
],
"title": "GetTopTracksResult",
"type": "object"
} # get_top_tracks_by_artist read
Get the user's top tracks for one specific artist, filtered from their full top-tracks list.
Full description
Get the user's top tracks for a specific artist.
Internally pages through all top tracks and filters by artist name
(case-insensitive). Returns up to 1000 tracks scanned.
Args:
artist_name: Artist name to filter by
range: Time window. Defaults to 'all_time'. Note that LB-aligned
tokens like 'week'/'month'/'year' mean the PREVIOUS FULL
calendar period, not the last N days.
username: ListenBrainz username (defaults to authenticated user) | Parameter | Type | Required | Description |
|---|---|---|---|
artist_name | string | Yes | Artist name to filter by (case-insensitive match) |
range | "week" | "month" | "quarter" | "half_yearly" | "year" | "all_time" | "this_week" | "this_month" | "this_year" | No | Time window for the per-artist top-tracks slice. Previous full calendar period (LB-aligned): 'week', 'month', 'quarter', 'half_yearly', 'year'. Current period (period start to now): 'this_week', 'this_month', 'this_year'. Open-ended: 'all_time'. All period boundaries are UTC-aligned. For users in non-UTC zones the boundary instant differs from local midnight — e.g. 'this_week' for a Pacific user starts at Sunday 17:00 local (Monday 00:00 UTC). This is rarely worth correcting for ranking endpoints (a few hours' worth of listens won't reorder a top-N), but matters for tools that bin per-listen. get_daily_activity exposes a `timezone` parameter that actually shifts bucket alignment to the user's zone — pass it when known. get_listening_activity accepts the same param for consistency but its time-series bucket boundaries remain UTC-aligned regardless (rebinning is deferred work — use get_recent_listens + client-side bucketing if exact local boundaries matter). Note: 'week'/'month'/'year' mean the previous FULL calendar period, not the last N days. Default: "all_time" |
username | string | null | No | ListenBrainz username to query. Defaults to the authenticated user. |
Parameter schema (JSON)
{
"properties": {
"artist_name": {
"description": "Artist name to filter by (case-insensitive match)",
"title": "Artist Name",
"type": "string"
},
"range": {
"default": "all_time",
"description": "Time window for the per-artist top-tracks slice. Previous full calendar period (LB-aligned): 'week', 'month', 'quarter', 'half_yearly', 'year'. Current period (period start to now): 'this_week', 'this_month', 'this_year'. Open-ended: 'all_time'. All period boundaries are UTC-aligned. For users in non-UTC zones the boundary instant differs from local midnight — e.g. 'this_week' for a Pacific user starts at Sunday 17:00 local (Monday 00:00 UTC). This is rarely worth correcting for ranking endpoints (a few hours' worth of listens won't reorder a top-N), but matters for tools that bin per-listen. get_daily_activity exposes a `timezone` parameter that actually shifts bucket alignment to the user's zone — pass it when known. get_listening_activity accepts the same param for consistency but its time-series bucket boundaries remain UTC-aligned regardless (rebinning is deferred work — use get_recent_listens + client-side bucketing if exact local boundaries matter). Note: 'week'/'month'/'year' mean the previous FULL calendar period, not the last N days.",
"enum": [
"week",
"month",
"quarter",
"half_yearly",
"year",
"all_time",
"this_week",
"this_month",
"this_year"
],
"title": "Range",
"type": "string"
},
"username": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "ListenBrainz username to query. Defaults to the authenticated user.",
"title": "Username"
}
},
"required": [
"artist_name"
],
"title": "GetTopTracksByArtistParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"DataWindow": {
"description": "Surfaces coverage information when a stats response may not\nfully cover the requested time range — most commonly when the\nresponse is empty and the LLM agent should retry with a different\n`range` token.\n\nPopulated on stats responses (top_artists/albums/tracks,\nlisten_count, daily/listening activity, etc.) by\n`_build_data_window` per toolforest_tools#1294. Currently\npopulated only for empty-response cases; see issue body for the\n\"obviously truncated but non-empty\" follow-up case (rolling-token\ncache wall) which is intentionally deferred.",
"properties": {
"requested_range": {
"description": "The `range` token the caller passed in.",
"title": "Requested Range",
"type": "string"
},
"note": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Human-readable explanation of why the response may not fully cover the requested range. LLM agents should relay this verbatim and consider the suggested retry. Common case: empty response because the user asked for `year` (previous full calendar year) but is a new account with no listens in that window — retry with `this_year`.",
"title": "Note"
}
},
"required": [
"requested_range"
],
"title": "DataWindow",
"type": "object"
},
"TopEntity": {
"description": "Shared model for top artists, albums, tracks.\n\n`mbid` holds the primary MusicBrainz identifier for the entity:\n - top artists → artist MBID\n - top albums → release MBID\n - top tracks → recording MBID\n\n`mbid` is `null` whenever the underlying listen records did not\ncarry a MusicBrainz ID. This is common in practice: scrobbles from\nSpotify and other connected services often arrive without MBIDs,\nand ListenBrainz does not back-fill them on the stats path. If you\nneed MBIDs to feed into MBID-keyed tools (get_similar_artists,\nadd_tracks_to_playlist, etc.), resolve names via\nlistenbrainz-artist_lookup or listenbrainz-metadata_lookup first\nrather than treating null `mbid` as a hard failure.\n\n`artist_mbid` is a convenience secondary field populated for top\nalbums and top tracks (where the primary `mbid` is the release or\nrecording MBID) so callers can resolve the artist without a second\ncall. For top artists it's the same value as `mbid`.",
"properties": {
"name": {
"title": "Name",
"type": "string"
},
"listen_count": {
"title": "Listen Count",
"type": "integer"
},
"artist_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Artist Name"
},
"release_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Release Name"
},
"mbid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Mbid"
},
"artist_mbid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Artist Mbid"
}
},
"required": [
"name",
"listen_count"
],
"title": "TopEntity",
"type": "object"
}
},
"properties": {
"success": {
"title": "Success",
"type": "boolean"
},
"tracks": {
"anyOf": [
{
"items": {
"$ref": "#/$defs/TopEntity"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Tracks"
},
"artist_name_matched": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Artist Name Matched"
},
"total_scanned": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Total Scanned"
},
"truncated": {
"default": false,
"title": "Truncated",
"type": "boolean"
},
"data_window": {
"anyOf": [
{
"$ref": "#/$defs/DataWindow"
},
{
"type": "null"
}
],
"default": null
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Error"
}
},
"required": [
"success"
],
"title": "GetTopTracksByArtistResult",
"type": "object"
} # get_user_profile read
Get the authenticated user's profile summary, including username and total listen count.
Full description
Get the connected user's profile summary (username and total listen count).
No parameters.
Parameter schema (JSON)
{
"properties": {},
"title": "GetUserProfileParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"properties": {
"success": {
"title": "Success",
"type": "boolean"
},
"username": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Username"
},
"listen_count": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Listen Count"
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Error"
}
},
"required": [
"success"
],
"title": "GetUserProfileResult",
"type": "object"
} # metadata_lookup read
Resolve (artist, title) pairs to MusicBrainz recording_mbids via exact + fuzzy lookup. Batch-capable, returns null per query when no match.
Full description
Resolve natural-language (artist, title) pairs to recording MBIDs.
Fuzzy-matches against our local MusicBrainz Aurora replica using
pg_trgm similarity on `recording.name` + `artist_credit.name` (and
optionally `release.name` when `release_name` is provided). Use this
whenever you have human-typed song references and need canonical
MBIDs — e.g. before building a playlist via `create_playlist` or
`add_tracks_to_playlist`.
Batch-first: pass up to 50 queries in one call; the implementation
fans out in parallel (5 concurrent by default) and dedupes
case-insensitively. Each result preserves the original query so
callers can zip results back to inputs even on partial misses.
Unresolved queries return `match: null` (NOT an exception) so a
single bad row doesn't break a batch — agents can decide per-track
whether to retry, prompt the user, or drop the entry.
Args:
queries: List of {artist_name, recording_name, release_name?} —
up to 50 per call. release_name, when supplied, narrows the
ranking to recordings that appear on a similarly-named
release; falls back to ignoring it if no release-filtered
candidate clears the similarity bar.
Returns:
results aligned 1:1 with the input queries; each entry has the
original query plus a match dict or null. The match contains
recording_mbid, recording_name, artist_credit_name, and (when a
release-filtered candidate won) release_mbid / release_name. | Parameter | Type | Required | Description |
|---|---|---|---|
queries | array<MetadataLookupQuery> | Yes | Up to 50 (artist, title, optional release) triples to resolve in one call. |
Parameter schema (JSON)
{
"$defs": {
"MetadataLookupQuery": {
"properties": {
"artist_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Artist name (e.g., 'Queen'). Fuzzy-matched. Provide this OR `artist`.",
"title": "Artist Name"
},
"artist": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Alias for `artist_name` (matches the FuzzyTrack shape used by `create_playlist`).",
"title": "Artist"
},
"recording_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Track / recording name (e.g., 'Bohemian Rhapsody'). Fuzzy-matched. Provide this OR `title`.",
"title": "Recording Name"
},
"title": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Alias for `recording_name` (matches the FuzzyTrack shape used by `create_playlist`).",
"title": "Title"
},
"release_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Optional release / album name to disambiguate covers and live versions. Aliased by `album`.",
"title": "Release Name"
},
"album": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Alias for `release_name` (matches the FuzzyTrack shape used by `create_playlist`).",
"title": "Album"
}
},
"title": "MetadataLookupQuery",
"type": "object",
"additionalProperties": false
}
},
"properties": {
"queries": {
"description": "Up to 50 (artist, title, optional release) triples to resolve in one call.",
"items": {
"$ref": "#/$defs/MetadataLookupQuery"
},
"maxItems": 50,
"minItems": 1,
"title": "Queries",
"type": "array"
}
},
"required": [
"queries"
],
"title": "MetadataLookupParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"MetadataLookupMatch": {
"properties": {
"recording_mbid": {
"description": "MusicBrainz recording ID (canonical track identifier).",
"title": "Recording Mbid",
"type": "string"
},
"recording_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Canonical recording name as stored in MusicBrainz. Null when the resolver tier that produced the match did not carry the name (e.g. ISRC-only resolution).",
"title": "Recording Name"
},
"artist_credit_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Canonical artist credit string (may include featured artists). Null under the same conditions as recording_name.",
"title": "Artist Credit Name"
},
"artist_mbids": {
"description": "MusicBrainz artist IDs for the credited artists.",
"items": {
"type": "string"
},
"title": "Artist Mbids",
"type": "array"
},
"release_mbid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "MusicBrainz release (album) ID, if known.",
"title": "Release Mbid"
},
"release_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Canonical release (album) name, if known.",
"title": "Release Name"
},
"confidence": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "Match-quality score. Populated for fuzzy-tier (Typesense) matches as the raw Typesense text_match score; null for exact-match tiers (ISRC / canonical), which have an implicit confidence of 1.0.",
"title": "Confidence"
}
},
"required": [
"recording_mbid"
],
"title": "MetadataLookupMatch",
"type": "object"
},
"MetadataLookupQuery": {
"properties": {
"artist_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Artist name (e.g., 'Queen'). Fuzzy-matched. Provide this OR `artist`.",
"title": "Artist Name"
},
"artist": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Alias for `artist_name` (matches the FuzzyTrack shape used by `create_playlist`).",
"title": "Artist"
},
"recording_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Track / recording name (e.g., 'Bohemian Rhapsody'). Fuzzy-matched. Provide this OR `title`.",
"title": "Recording Name"
},
"title": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Alias for `recording_name` (matches the FuzzyTrack shape used by `create_playlist`).",
"title": "Title"
},
"release_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Optional release / album name to disambiguate covers and live versions. Aliased by `album`.",
"title": "Release Name"
},
"album": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Alias for `release_name` (matches the FuzzyTrack shape used by `create_playlist`).",
"title": "Album"
}
},
"title": "MetadataLookupQuery",
"type": "object"
},
"MetadataLookupResult": {
"properties": {
"query": {
"$ref": "#/$defs/MetadataLookupQuery"
},
"match": {
"anyOf": [
{
"$ref": "#/$defs/MetadataLookupMatch"
},
{
"type": "null"
}
],
"default": null,
"description": "The resolved match, or null if LB couldn't fuzzy-match the query."
}
},
"required": [
"query"
],
"title": "MetadataLookupResult",
"type": "object"
}
},
"properties": {
"success": {
"title": "Success",
"type": "boolean"
},
"results": {
"anyOf": [
{
"items": {
"$ref": "#/$defs/MetadataLookupResult"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Results"
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Error"
}
},
"required": [
"success"
],
"title": "MetadataLookupResponse",
"type": "object"
} # move_tracks_in_playlist write
Move a block of tracks to a new position within a playlist. Index-based; use get_playlist to look up positions first.
Full description
Move a contiguous block of tracks to a new position within a playlist.
Both indices are zero-based. The block of `count` tracks at positions
`[from_index, from_index + count)` ends up at positions
`[to_index, to_index + count)` in the resulting list. The other tracks
shift to make room.
LB requires the recording MBID at `from_index` as a sanity check on
the move call (so concurrent edits can't move the wrong track). The
toolkit fetches the playlist, reads the MBID at `from_index`, and
sends both index and MBID — callers don't need to track MBIDs
themselves.
Use `get_playlist` first to look up the positions you want to move
between. Refuses no-op calls (`from_index == to_index`) — caller
almost certainly meant something else.
Only the playlist's creator can modify it; LB returns a 403 otherwise.
Args:
playlist_mbid: Playlist MBID
from_index: Zero-indexed start position of the block to move
to_index: Zero-indexed destination position
count: Number of contiguous tracks to move (default 1) | Parameter | Type | Required | Description |
|---|---|---|---|
playlist_mbid | string | Yes | Playlist MBID |
from_index | integer | Yes | Zero-indexed start position of the track(s) to move. Must be in range [0, len(playlist) - count]. |
to_index | integer | Yes | Zero-indexed destination position. The moved block ends up at this index in the resulting list. Must be in range [0, len(playlist) - count]. |
count | integer | No | Number of contiguous tracks to move starting at `from_index`. Defaults to 1 (single-track move). Default: 1 |
Parameter schema (JSON)
{
"properties": {
"playlist_mbid": {
"description": "Playlist MBID",
"title": "Playlist Mbid",
"type": "string"
},
"from_index": {
"description": "Zero-indexed start position of the track(s) to move. Must be in range [0, len(playlist) - count].",
"minimum": 0,
"title": "From Index",
"type": "integer"
},
"to_index": {
"description": "Zero-indexed destination position. The moved block ends up at this index in the resulting list. Must be in range [0, len(playlist) - count].",
"minimum": 0,
"title": "To Index",
"type": "integer"
},
"count": {
"default": 1,
"description": "Number of contiguous tracks to move starting at `from_index`. Defaults to 1 (single-track move).",
"minimum": 1,
"title": "Count",
"type": "integer"
}
},
"required": [
"playlist_mbid",
"from_index",
"to_index"
],
"title": "MoveTracksInPlaylistParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"properties": {
"success": {
"title": "Success",
"type": "boolean"
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Error"
}
},
"required": [
"success"
],
"title": "MoveTracksInPlaylistResult",
"type": "object"
} # remove_tracks_from_playlist write
Remove tracks from a playlist by recording MBID. Removes ALL occurrences of each MBID; missing MBIDs are reported in not_found.
Full description
Remove tracks from an existing playlist by recording MBID.
LB's playlist-item delete endpoint takes (index, count) ranges, not
MBIDs. This tool fetches the current playlist, maps requested MBIDs to
their positions, then issues delete calls from highest index to lowest
so subsequent deletes don't have to renumber against earlier ones.
All occurrences of each requested MBID are removed (so a track listed
twice has both copies removed). MBIDs that weren't in the playlist
are quietly skipped and surfaced via the `not_found` field on the
result — caller can decide whether to surface them as a UI warning.
Only the playlist's creator can modify it; LB returns a 403 otherwise.
Args:
playlist_mbid: Playlist MBID
recording_mbids: List of recording MBIDs to remove | Parameter | Type | Required | Description |
|---|---|---|---|
playlist_mbid | string | Yes | Playlist MBID |
recording_mbids | array<string> | Yes | Recording MBIDs to remove. ALL occurrences of each MBID are removed (a track listed twice gets both copies removed). MBIDs not present in the playlist are quietly ignored — see `not_found` on the result. |
Parameter schema (JSON)
{
"properties": {
"playlist_mbid": {
"description": "Playlist MBID",
"title": "Playlist Mbid",
"type": "string"
},
"recording_mbids": {
"description": "Recording MBIDs to remove. ALL occurrences of each MBID are removed (a track listed twice gets both copies removed). MBIDs not present in the playlist are quietly ignored — see `not_found` on the result.",
"items": {
"type": "string"
},
"title": "Recording Mbids",
"type": "array"
}
},
"required": [
"playlist_mbid",
"recording_mbids"
],
"title": "RemoveTracksFromPlaylistParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"properties": {
"success": {
"title": "Success",
"type": "boolean"
},
"removed": {
"default": 0,
"description": "Number of items removed from the playlist.",
"title": "Removed",
"type": "integer"
},
"not_found": {
"description": "Recording MBIDs that were requested for removal but weren't in the playlist. Empty list when every requested MBID was found.",
"items": {
"type": "string"
},
"title": "Not Found",
"type": "array"
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Error"
}
},
"required": [
"success"
],
"title": "RemoveTracksFromPlaylistResult",
"type": "object"
} # search_playlists read
Search for public ListenBrainz playlists by name.
Full description
Search for public playlists by name.
Args:
query: Search query
count: Number of results (default 25)
offset: Offset for pagination | Parameter | Type | Required | Description |
|---|---|---|---|
query | string | Yes | Search query (playlist name) |
count | integer | No | Number of results Default: 25 |
offset | integer | No | Offset for pagination Default: 0 |
Parameter schema (JSON)
{
"properties": {
"query": {
"description": "Search query (playlist name)",
"title": "Query",
"type": "string"
},
"count": {
"default": 25,
"description": "Number of results",
"maximum": 100,
"minimum": 1,
"title": "Count",
"type": "integer"
},
"offset": {
"default": 0,
"description": "Offset for pagination",
"minimum": 0,
"title": "Offset",
"type": "integer"
}
},
"required": [
"query"
],
"title": "SearchPlaylistsParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"PlaylistSummary": {
"properties": {
"playlist_mbid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Playlist Mbid"
},
"playlist_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Playlist Id"
},
"source": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Source"
},
"title": {
"title": "Title",
"type": "string"
},
"creator": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Creator"
},
"track_count": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Track Count"
},
"created_at": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Created At"
},
"description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Description"
},
"playlist_url": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Shareable web URL for the playlist. Derived for sources where the URL format is stable (listenbrainz, spotify). Spotify URLs are always constructable from the platform ID, but private Spotify playlists are only accessible to their owner. None for apple_music and soundcloud where the URL needs region/slug data that isn't always in the API response.",
"title": "Playlist Url"
}
},
"required": [
"title"
],
"title": "PlaylistSummary",
"type": "object"
}
},
"properties": {
"success": {
"title": "Success",
"type": "boolean"
},
"playlists": {
"anyOf": [
{
"items": {
"$ref": "#/$defs/PlaylistSummary"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Playlists"
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Error"
}
},
"required": [
"success"
],
"title": "SearchPlaylistsResult",
"type": "object"
} # search_users read
Search ListenBrainz users by username and return matching user results.
Full description
Search for ListenBrainz users by username.
Args:
search_term: Username to search for | Parameter | Type | Required | Description |
|---|---|---|---|
search_term | string | Yes | Username to search for |
Parameter schema (JSON)
{
"properties": {
"search_term": {
"description": "Username to search for",
"title": "Search Term",
"type": "string"
}
},
"required": [
"search_term"
],
"title": "SearchUsersParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"properties": {
"success": {
"title": "Success",
"type": "boolean"
},
"users": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Users"
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Error"
}
},
"required": [
"success"
],
"title": "SearchUsersResult",
"type": "object"
} # update_playlist write
Update a playlist's title, description, or public flag. Only the creator can update; partial updates supported.
Full description
Update a playlist's metadata (title, description, public flag).
Partial-update semantics: only the fields you supply are changed; the rest
remain as they were on the server. To clear the description, pass
`clear_description=true` rather than `description=""` (empty `description`
is treated as "not provided" so callers can't accidentally wipe a
description by passing an unset field).
Only the playlist's creator can update it; LB returns a 403 otherwise.
Args:
playlist_mbid: Playlist MBID to update
title: New title (omit to leave unchanged)
description: New description (omit to leave unchanged)
public: New visibility (omit to leave unchanged)
clear_description: True clears the existing description; ignored if
`description` is also set | Parameter | Type | Required | Description |
|---|---|---|---|
playlist_mbid | string | Yes | Playlist MBID to update |
title | string | null | No | New playlist title. Omit to leave the existing title unchanged. Empty string is treated as 'not provided' rather than 'set to empty'. |
description | string | null | No | New playlist description (free-form). Omit to leave unchanged. Pass an empty string explicitly via `clear_description=true` if you want to remove an existing description. |
public | boolean | null | No | Toggle public/private visibility. True makes the playlist public (searchable + shareable URL); False makes it private. Omit to leave the existing visibility unchanged. |
clear_description | boolean | No | If True, clears the existing description (sets to empty string). Use this instead of `description=''` since empty `description` is treated as 'not provided'. Ignored if `description` is also set. Default: false |
Parameter schema (JSON)
{
"properties": {
"playlist_mbid": {
"description": "Playlist MBID to update",
"title": "Playlist Mbid",
"type": "string"
},
"title": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "New playlist title. Omit to leave the existing title unchanged. Empty string is treated as 'not provided' rather than 'set to empty'.",
"title": "Title"
},
"description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "New playlist description (free-form). Omit to leave unchanged. Pass an empty string explicitly via `clear_description=true` if you want to remove an existing description.",
"title": "Description"
},
"public": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"description": "Toggle public/private visibility. True makes the playlist public (searchable + shareable URL); False makes it private. Omit to leave the existing visibility unchanged.",
"title": "Public"
},
"clear_description": {
"default": false,
"description": "If True, clears the existing description (sets to empty string). Use this instead of `description=''` since empty `description` is treated as 'not provided'. Ignored if `description` is also set.",
"title": "Clear Description",
"type": "boolean"
}
},
"required": [
"playlist_mbid"
],
"title": "UpdatePlaylistParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"properties": {
"success": {
"title": "Success",
"type": "boolean"
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Error"
}
},
"required": [
"success"
],
"title": "UpdatePlaylistResult",
"type": "object"
}