GET /api/v1/lyrics/status # Session
How far the lyric language pass has got.
Scope: session only.
Tapedeck fetches lyrics from LRCLIB for one
reason: the language a transliterated title cannot give. Script
detection settles any non-Latin title with certainty and gives up on
Latin text, so a romanised title falls through to English/Other, which
is a shrug rather than a measurement and is most of a real
transliterated history. The lyric is where that title's own script went.
Lyrics are stored in the MusicBrainz cache database rather than in the
listening database, so GET /api/v1/backup does not carry them —
they are third-party copyrighted text, not the user's listening.
instrumental is counted separately because it is a real answer: a
track with no words has no language, and folding it into "found" would
report a finished lookup as an unfinished one.
Responses
application/json
object
How far the lyric language pass has got. The three cache counts are absent
when the cache cannot be read — unknown, not zero.
available boolean required
why string | null
Why not, when not available.
asked integer | null · int64
Tracks LRCLIB has been asked about.
with_lyrics integer | null · int64
instrumental integer | null · int64
A real answer — a track with no words has no language.
unresolved integer · int64 required
Tracks still filed under English/Other that the pass could try.
GET /api/v1/mbids/status # Session
MusicBrainz recording coverage.
Session-only.
A recording MBID is written by flush_pending alone, which only ever
sees listens on their way to a sink — so an imported listen never got
one, and nothing else writes the column. On a mostly-imported history
that leaves most of the listening unidentified, and with it excluded from
every backfill that keys off a recording: ISRC, work-language, release
year.
unmatched_tracks is the reason this reports three numbers instead of
two. It counts distinct tracks MusicBrainz was asked about and had
nothing for — real listening simply not in their database. Without it a
bar that stops short reads as a stall rather than as an answer.
Responses
application/json
object
Recording-level MusicBrainz coverage of your history.
total integer · int64 required
resolved integer · int64 required
Listens carrying a recording MBID.
pending integer · int64 required
unmatched_tracks integer · int64 required
Distinct tracks MusicBrainz was asked about and had nothing for — real
listening simply not in their database. Without it a progress bar that
stops short reads as a stall rather than as an answer.
401 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
object
Every error body in the API has this shape.
code integer · int32 required
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."
}
POST /api/v1/mbids/backfill # Session
Resolve missing recording MBIDs now.
Session-only, admin only. Runs in the background; poll
/api/v1/mbids/status for progress.
This is the pass that unblocks the others, so it is worth running before
the ISRC or language backfills on a history that came from an import.
A name search can be wrong, and a wrong MBID is worse than none — it
would be written onto every play of the track and then poison the ISRC
and language lookups downstream, with nothing erroring. So a result is
only accepted when it agrees with the question: the artist matches,
and the title matches once a trailing qualifier is stripped. Misses are
negative-cached, so a track already asked about is not asked again while
that answer is current.
Responses
202 Started. status is started.
application/json
object
An acknowledgement with nothing else to report.
status names what happened — ok, updated, deleted, cleared,
started and so on; the operation says which it sends. A client needs only
the HTTP status to know it worked.
401 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
object
Every error body in the API has this shape.
code integer · int32 required
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."
}
403 Authenticated, but not permitted. Either the token lacks the required
scope, or the endpoint needs the admin role. Deliberately not a 401 —
re-authenticating will not help.
application/json
object
Every error body in the API has this shape.
code integer · int32 required
error string required
Human-readable. Not a stable identifier — do not branch on it.
{
"code": 403,
"error": "This token does not have the 'write' scope"
}
POST /api/v1/artwork/backfill # Session
Run the artwork backfill now.
Session-only. Providers return URLs, never bytes — Deezer's terms
discourage persisting artwork. The queue keys on artwork_url only;
caa_id is written by three other paths, so keying on it silently marks
rows done that have no URL.
Responses
202 Started. status is started.
application/json
object
An acknowledgement with nothing else to report.
status names what happened — ok, updated, deleted, cleared,
started and so on; the operation says which it sends. A client needs only
the HTTP status to know it worked.
401 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
object
Every error body in the API has this shape.
code integer · int32 required
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."
}
403 Authenticated, but not permitted. Either the token lacks the required
scope, or the endpoint needs the admin role. Deliberately not a 401 —
re-authenticating will not help.
application/json
object
Every error body in the API has this shape.
code integer · int32 required
error string required
Human-readable. Not a stable identifier — do not branch on it.
{
"code": 403,
"error": "This token does not have the 'write' scope"
}
Preview metadata fixes.
Session-only. Preview-then-apply, never automatic — and the preview
deliberately exposes how weak each guess is, because the operator has to
judge it.
Three passes. Mixed releases: one listen out of twelve routinely
arrives carrying a compilation's release MBID. The whole ballot is
returned, most-claimed first, so "15 releases, plurality of 4" is
visible as the guess it is. Featured credits: Artist feat. X is a
different artist to every query in the app; only the explicit
feat./ft./featuring/with forms match, because "Simon &
Garfunkel" is a name, not a credit. Spelling variants: grouped on the
exact string, with case_only flagged — a two-stage filter whose second
stage was coarser than the first once made every case-only variant
invisible.
Responses
200 The proposed changes, each individually selectable.
application/json
object
What the sanitiser would change. Nothing has been changed.
mixed_albums array required
Albums whose listens disagree about which release they are.
each item object
An album whose listens disagree about which release they belong to.
plays integer · int64 required
variants integer · int64 required
How many distinct release MBIDs the listens carry. Always > 1 here.
winner string | null
The MBID the most listens agree on — the proposed fix, not a verdict.
winner_plays integer · int64 required
untagged integer · int64 required
Listens of this album carrying no release MBID at all. Unifying stamps
these with the chosen release too, so they're reported separately.
candidates array required
Every release the listens claim, most-claimed first, so the operator can
pick a different one than the plurality.
each item object
One release an album's listens claim to be, and how many say so.
plays integer · int64 required
featured_artists array required
Artist strings carrying a featured credit, and the artist they fold
into.
each item object
An artist string carrying a featured credit, and the primary artist it would
fold into.
plays integer · int64 required
artist_variants array required
Artists with other spellings of themselves in the same history.
each item object
An artist name with other spellings of itself in the same history.
primary string required
The most-played spelling — the one the others fold into.
primary_plays integer · int64 required
variants array required
(spelling, plays) for every other form of the same name.
each item tuple · [string, integer · int64]
case_only boolean required
These differ only in capitalisation or spacing.
Worth separating because the consequences differ. A case-only variant
costs nothing but display consistency — every read path that counts
already lowercases. Anything else ("Cœur de pirate" against "Coeur de
pirate") is genuinely two artists to every query, and splits the plays.
split_attributions array required
Tracks carrying two different artists on the same record.
each item object
One track carrying two different artists on the same record.
title string required
Lowercased grouping keys.
primary string required
The record's own artist, and what everything else folds into.
primary_plays integer · int64 required
This artist's plays on this track.
primary_album_plays integer · int64 required
This artist's plays across the whole album.
primary_album_tracks integer · int64 required
How many distinct tracks of the album carry this artist. This is why
they win, and it is shown so a weak case is visible as one: a director
credited on twelve tracks is obvious, a winner with two is a guess.
folding array required
(artist, plays) for every other attribution on this track.
each item tuple · [string, integer · int64]
401 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
object
Every error body in the API has this shape.
code integer · int32 required
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."
}
Apply selected metadata fixes.
Session-only. Explicit lists select individual edits; the booleans mean
"everything in this category". A non-empty list wins over its
boolean — asking for three edits and getting four hundred would be the
worst reading of an ambiguous request.
The featured-credit target is derived, never taken from the request,
or this would become "rename any artist to anything". A variant merge is
refused unless both spellings normalise to the same key, so merging
"Burial" into "Aphex Twin" cannot happen.
A spelling merge is not reversible. The feat. fold keeps the
original in artist_credit; a rename keeps nothing, because both
strings name the same artist.
Request body required
application/json
object
What to apply.
Two ways to say it, because both are the common case at different times: the
booleans mean "everything in this category", the lists name individual edits.
A non-empty list wins over its boolean — asking for specific edits and
getting all of them would be the worst possible reading.
album_edits array
each item object
One album to unify, and onto which release.
mbid_release is carried per edit rather than re-derived, so the operator
can overrule the plurality — which is the point of reviewing at all.
mbid_release string required
credit_edits array
each item object
artist string required
The full credit as stored, e.g. Cœur de pirate feat. Loud.
variant_edits array
each item object
from string required
The spelling being retired.
split_edits array
each item object
One track whose stray attribution goes back to the record's own artist.
from string required
The attribution being folded — a singer, on a soundtrack.
to string required
The record's artist. Checked against the preview rather than trusted:
taking this from the request would turn a narrow fix into "rename any
track to any artist", the same reason split_featured derives its own
target.
Responses
application/json
object
What the sanitiser changed: how many of each fix, and how many listens
each touched.
albums_fixed integer · int64 required
album_listens_changed integer · int64 required
artists_folded integer · int64 required
artist_listens_changed integer · int64 required
variants_merged integer · int64 required
variant_listens_changed integer · int64 required
splits_merged integer · int64 required
split_listens_changed integer · int64 required
400 Malformed or rejected input.
application/json
object
Every error body in the API has this shape.
code integer · int32 required
error string required
Human-readable. Not a stable identifier — do not branch on it.
401 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
object
Every error body in the API has this shape.
code integer · int32 required
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."
}