GET /api/v1/albums/{id}/tracklist # Token reador Session
The record's own tracklist, including tracks never played.
Scope: read (since v0.120). Everything else on the album page is aggregated from
listens, so a record played twice renders as a two-track record. This
asks MusicBrainz what is actually on it.
Nothing is stored. The caller matches these against the plays it
already holds; writing a tracklist into scrobbles would invent listens
that never happened. It is a separate call rather than part of
/api/v1/albums/{id} because it costs a rate-limited request to an
external service and the page must render without one.
The release is chosen in order: the entity's own MBID, then the release
MBID a plurality of the album's listens claim (a tagger works per
track, so one listen in twelve routinely carries a compilation's), then
a search, which is a guess.
404 is a normal answer — a great deal of any real library is not in
MusicBrainz, and those are exactly the records worth keeping.
Responses
application/json
object
The record as MusicBrainz has it, including tracks you have never played.
Nothing is stored.
mbid_release string | null
year integer | null · int64
tracks array required
A CD is one side, a double LP four.
each item object
One track on a physical side.
side string required
"A", "B", "C"… Cassettes have two, a double LP four.
position integer · int64 required
Position within the side, from 1.
duration_secs integer | null · int64
disc integer | null · int64
Which physical disc of the set this track is on, from 1.
Null means nobody recorded which disc, not disc 1.
400 That entity is not an album.
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."
}
404 No matching release, or no such album.
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.
502 MusicBrainz could not be reached.
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.
PUT /api/v1/artists/landscape/{id} # Session
Set or clear an artist's wide image.
Session-only. Takes the artist row id (artist_row_id on the artist
page), not the entity id — the artists row is keyed by name and shared
across users, which is what the portrait proxy addresses too.
A different photograph from the portrait rather than a crop of it: the
Reports chapter tile is 16/11, where a square either pillarboxes or
crops the head off. Nothing Tapedeck can reach supplies a landscape, so
it is present only where the user chose one, and a chapter with none
keeps the enormous faint initial — that is the design, not a placeholder.
An empty string clears it.
Request body required
application/json
object
url string required
An http(s) URL, or a path on this server such as
/api/v1/artists/img/12 or /api/v1/art/914 — which is how you point
at your own library through the proxy. Empty clears it.
Responses
200 Saved. Null when cleared.
application/json
object
A wide image, set or cleared. Null when cleared.
landscape_url string | null
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."
}
404 No such resource, or it belongs to another user.
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.
PUT /api/v1/albums/{id}/cover # Session
Override an album cover.
Session-only. The counterpart of the artist portrait override: an album
otherwise wears whatever artwork the newest listen of it happens to
carry, which is unfixable when it is the wrong edition's.
400s when the id is not a release entity — an artist's picture has
its own endpoint, and two places to set one would leave no rule about
which wins. Like every entity write, this is not scoped per user:
entities is shared.
Request body required
application/json
object
url string required
An http(s) URL, or a path on this server such as
/api/v1/artists/img/12 or /api/v1/art/914 — which is how you point
at your own library through the proxy. Empty clears it.
Responses
200 Saved. Null when cleared.
application/json
object
An album cover, set or cleared. Null when cleared — the page then falls
back to the cover from your listens.
artwork_url string | null
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."
}
404 No such album, or that entity is not a release.
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.
GET /api/v1/art/upload/{name} # Token reador Session
Serve an uploaded picture.
Scope: read (since v0.120). Cached private, immutable — the name is random per
upload, so the bytes behind a given URL never change.
name is validated, not trusted: it must be exactly the 24 hex
characters this server generates plus a known image extension. The
override columns accept any /-prefixed string, so a route that read a
path out of one would serve arbitrary files from the uploads directory.
Parameters
name path required
As it appears in the stored URL.
Responses
200 Image bytes, Cache-Control: private, immutable.
image/*
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."
}
404 No such picture, or the file is no longer on this server.
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.
POST /api/v1/artists/img/{id}/upload # Session
Upload an artist portrait.
Session-only. The counterpart of the PUT above, for a picture that is
on your disk rather than already somewhere on the web.
The extension is taken from the declared content type, never from
the filename — the path is one this server writes. The stored name is
random, so the served URL is immutable-cacheable and a replaced
picture is a new URL rather than the same URL with new bytes.
Replaces the override exactly as a pasted URL does, and the file it
supersedes is deleted. artists carries no user column, so this is an
instance-wide edit.
Request body required
multipart/form-data
object
A multipart/form-data upload of one file.
The server takes the first file part whatever its field name; file is the
name to use. Its declared Content-Type decides how the file is stored — a
filename from the client never reaches a path on disk.
file string · binary required
Responses
200 Saved. The body carries the new URL, which is a path on this server.
application/json
object
A portrait, set or cleared. Null when cleared.
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."
}
404 No such resource, or it belongs to another user.
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.
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.
415 Not an image type this server stores (JPEG, PNG, WebP, GIF, AVIF, TIFF).
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.
POST /api/v1/artists/landscape/{id}/upload # Session
Upload an artist's wide picture.
Session-only. Same rules as the portrait upload.
This is the one image override nothing can fill automatically — no
provider Tapedeck reaches supplies a landscape — so before this it was a
URL or the big initial.
Request body required
multipart/form-data
object
A multipart/form-data upload of one file.
The server takes the first file part whatever its field name; file is the
name to use. Its declared Content-Type decides how the file is stored — a
filename from the client never reaches a path on disk.
file string · binary required
Responses
200 Saved. The body carries the new URL, which is a path on this server.
application/json
object
A wide image, set or cleared. Null when cleared.
landscape_url string | null
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."
}
404 No such resource, or it belongs to another user.
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.
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.
415 Not an image type this server stores (JPEG, PNG, WebP, GIF, AVIF, TIFF).
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.
POST /api/v1/albums/{id}/cover/upload # Session
Upload an album cover.
Session-only. Same rules as the artist uploads. Takes an entity id.
Replaces the derived cover the way the PUT does, so the page still
offers Reset (back to whatever the listens carry) rather than Clear.
Entities are shared, so this is an instance-wide edit.
Request body required
multipart/form-data
object
A multipart/form-data upload of one file.
The server takes the first file part whatever its field name; file is the
name to use. Its declared Content-Type decides how the file is stored — a
filename from the client never reaches a path on disk.
file string · binary required
Responses
200 Saved. The body carries the new URL, which is a path on this server.
application/json
object
An album cover, set or cleared. Null when cleared — the page then falls
back to the cover from your listens.
artwork_url string | null
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."
}
404 No such resource, or it belongs to another user.
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.
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.
415 Not an image type this server stores (JPEG, PNG, WebP, GIF, AVIF, TIFF).
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.
GET /api/v1/art/library # Token writeor Session
Does this album already have a cover?
Scope: write.
The precondition of an upload, so the common case costs one small GET
rather than a megabyte of JPEG. A client reads the picture off disk only
when this answers false.
write, not read — this exists only to decide a write, and
requiring read would force a scrobbling token that wants to offer a
sleeve to carry read access to the whole listening history as well.
held is true when any listen of this album carries artwork (a
Cover Art Archive id counts, -1 excluded), or when a cover has been
set by hand on the album — one set that way is what the album page
prefers over everything else, so answering false would invite a
picture that would never be shown.
Deliberately not album_artwork's question, which excludes skips
because its job is choosing a cover to display. An album whose only
listens are skips would otherwise answer "nothing held" for ever and the
same sleeve would be re-uploaded on every run.
Creates nothing. GET /api/v1/resolve would mint the entity as a side
effect of being asked, which is right for a browser about to annotate
something and wrong for a player asking whether to bother.
Responses
200 Whether a cover is already held.
application/json
object
Whether the album already has a cover.
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."
}
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/art/library # Token writeor Session
Offer a cover read from the client's own files.
Scope: write.
Writes scrobbles.artwork_url on every listen of this album that has
none, and nothing else. That column is the artwork backfill's queue
and the album page reads it back, so a sleeve arriving with the listens
is exactly what a Plex-sourced listen carries. A cover override would
claim somebody set it by hand, and nobody did.
Rows holding the artwork_url = '' tombstone are filled too, and that
is the point rather than an oversight: the tombstone means "asked every
provider, nobody had a picture", and the premise here is that the
listener's own file does. Skipped listens are filled as well — artwork
is not a count.
The held check runs again before the bytes are written, so a cover that
arrived between the GET and this costs no file on disk and is reported
rather than treated as an error.
Request body required
multipart/form-data
object
A cover a player read off its own files, offered as multipart/form-data.
artist string required
The album's artist, as you would submit it.
album string required
The album, as you would submit it.
file string · binary required
The image. Its declared Content-Type decides how it is stored.
release_mbid string | null
Accepted and not read — the cover is keyed on names like every other
album query. Send the MBID on a listen's additional_info instead.
Responses
application/json
What became of an offered cover.
one of option 1 object
Something filled the cover since the client asked; nothing was stored.
already_held boolean required
option 2 object
artwork_url string required
Where the stored picture is served from.
listens_covered integer · int64 required
Your listens of the album that now carry it.
400 No image, or a blank artist or album.
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."
}
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"
}
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.
415 Not an image type this server stores.
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.