object
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
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.
timestamp integer · int64 required
Unix seconds, UTC, start of play.
duration integer | null · int64
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
format_type string | null
bitrate integer | null · int32
sample_rate integer | null · int32
bit_depth integer | null · int32
channels integer | null · int32
source_quality string | null
is_lossless boolean | null
dsd_rate integer | null · int64
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
delivery_sample_rate integer | null · int32
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
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.