Export your listens.
Session-only. Your whole history, newest first, streamed in pages so a
large one cannot exhaust the server's memory. JSON by default — an array of
Listen, the shape the importer reads back — or CSV with
title,artist,album,format,chain,mbid,time.
Parameters
format query csv, or anything else for JSON — the default.
Responses
An attachment, tapedeck-export.json or .csv.
application/json
A stored listen. The quality columns are flat, not nested under a
quality object — the frontend reads them raw so its own qualityLabel()
produces the same tag everywhere, and a server-side copy of that logic
would drift.
The audio fields are null for most of any real history: they are only ever written by ingest and the session-shaped sources, so an imported listen carries none of them. Any share computed over them is a share of that population, not of the listening.
id integer · int64 required user_id integer · int64 required title string required artist string required The primary artist. Everyone credited lives in scrobble_artists and is
what the artist pages and charts aggregate over, so a listen crediting a
soloist appears under both names there while this field still names the
one the record is filed under.
album string | null timestamp integer · int64 required Unix seconds, UTC, start of play.
duration integer | null · int64 Seconds.
source_id string required Dedup key. Unique per user per play.
source_name string required Plex, Jellyfin, Navidrome, ingest:<client>, Import…
status string required pending is queued for forwarding, synced has been forwarded, and
imported is stored only and will never be forwarded. Both an import
and a skip land as imported — forwarding either would put a wrong
scrobble on a permanent public record.
There is no sent and no failed. A delivery that fails stays
pending and is retried, and which sinks it has already reached is in
delivered_sinks — that is what stops a partial failure
double-scrobbling.
mbid_recording string | null mbid_release string | null mbid_artist string | null A JSON-encoded array, not an array — the column stores what was written to it. Parse it before use.
mbid_release_group string | null The release group, which is what relates a reissue, a remaster and a
regional edition to one another. Filled from a submitting client's
additional_info, a ListenBrainz export's mbid_mapping, or a
Jellyfin/Emby session — never by a lookup.
mbid_release_track string | null The track on a particular release, as distinct from the recording: the same recording is a different release-track on an album, a single and a compilation. Only a tagging client ever knows it.
mbid_work string | null The composition rather than a performance of it — the identifier that relates every recording of one piece. Only a tagging client ever knows it.
mbid_album_artist string | null A JSON-encoded array, like mbid_artist. Whoever the record is
credited to, which on a compilation or a soundtrack is not the
performer of the track.
caa_id integer | null · int64 Cover Art Archive id. -1 is a tombstone meaning "asked, no artwork
exists" — treat any non-positive value as no artwork.
caa_release_mbid string | null isrc string | null format_type string | null pcm, dsd, mqa.
codec string | null bitrate integer | null · int32 kbps.
sample_rate integer | null · int32 Hz.
bit_depth integer | null · int32 channels integer | null · int32 container string | null source_quality string | null is_lossless boolean | null dsd_rate integer | null · int64 Hz.
dsd_multiplier integer | null · int32 dsd_to_pcm_converted boolean | null delivery_codec string | null Set only on a genuine transcode.
delivery_bitrate integer | null · int32 kbps.
delivery_sample_rate integer | null · int32 Hz.
delivery_bit_depth integer | null · int32 is_transcoded boolean | null transcode_reason string | null quality_score number | null · double 0–100, computed once at ingest and never recomputed. Null for anything imported, so an average over it has the same narrow population as the audio fields.
device_id integer | null · int64 chain_id integer | null · int64 The resolved signal chain.
chain_variant_id integer | null · int64 Which setup of that chain was fitted.
session_id integer | null · int64 listening_context string | null From the resolved chain.
submission_client string | null track_number integer | null · int32 artwork_url string | null Empty string is a tombstone, same idea as caa_id: -1.
source_ref integer | null · int64 The user_sources row this was polled from.
listened_ms integer | null · int64 Milliseconds actually heard, paused time excluded. Only a pushing
client can report this, so it is null for everything polled and
everything imported. What skipped cannot express — "cut short three
seconds in" and "cut short with thirty seconds left" are opposite
behaviours.
is_shuffle boolean | null Shuffle chose this, not the listener.
queue_source string | null What it played from, in the client's own words.
company string | null Who this was heard with, as names. Filled automatically for a listen
that happened inside a Shared Spool, and by hand via
POST /api/v1/scrobbles/bulk-company otherwise.
null means unmarked, not alone. Nobody recorded who was there —
which is true of every listen predating the field. Counting marked
listens is exact; reporting a share of listening that was solitary is
not, and would be a fact about how much has been marked.
joint_session_id string | null The Shared Spool this listen belonged to, as the spool's uuid. A joint listen is N rows across N decks sharing this — never one row with several owners. Only a sitting ever sets it.
delivered_sinks string | null JSON array of the sinks this has already reached, so a retry only hits the ones that have not. A string, not an array.
text/csv
No valid session cookie or token. Also returned when a token is presented to a session-only endpoint — the endpoint does not accept tokens at all, so the scope is irrelevant.
application/json
Every error body in the API has this shape.
code integer · int32 required Mirrors the HTTP status.
error string required Human-readable. Not a stable identifier — do not branch on it.
{
"code": 401,
"error": "Authentication required. Log in to access this endpoint."
}