← API reference

Library

Album, artist and entity pages.

16 of 16 · v0.120.0
GET /api/v1/albums/{id} #
Token reador Session

An album page.

Scope: read (since v0.120). Aggregated over scrobbles by name. Track rows carry the raw quality columns, not a formatted label — the client formats them, so a server-side copy of that logic cannot drift.

An entity with zero plays still renders rather than 404ing: something annotated once and since deleted from the history is still a real record.

Parameters

id path required

The album's entity id, from GET /api/v1/resolve.

integer · int64

Responses

200

OK.

application/json

object

An album page: your listening of one record.

id integer · int64 required

The entity id.

kind string required

Always album.

title string required
artist string required
artist_entity_id integer | null · int64

The artist's page.

mbid string | null
year integer | null · int32
track_count integer · int64 required
listening_seconds integer · int64 required

Time you spent on this record, not the record's length — that is the sum of the distinct track durations in tracks.

plays integer · int64 required
artwork_url string | null
caa_id integer | null · int64
caa_release_mbid string | null
cover_override boolean required

A cover was chosen by hand, so offer "reset to the cover from your listens" rather than only "clear".

loved boolean required
bio object required

A description of the album or artist, with the attribution it must be shown with. Every field is null when no provider had one.

text string | null
attribution string | null

Shown with the text, always — Last.fm's is CC BY-SA, and one without the other breaks the licence.

provider string | null
url string | null

Where the full text lives.

stats array required

Four cards: plays, hours, first heard, and this record's share of the artist.

each item
object

One stat card, phrased by the server so every surface says "412 plays" the same way.

k string required

The label — Your Plays, Hours, First Heard, Share.

v string required

The figure, formatted.

sub string required

The line under it.

tone string required

plain or dark.

tracks array required

The tracks you have played, with how they sounded.

each item
object

One distinct track. The quality columns are carried raw so the frontend's existing qualityLabel() produces the same tag it does on a history row — duplicating that logic server-side would let the two drift.

title string required
plays integer · int64 required
duration integer | null · int64
track_number integer | null · int32
album string | null

Set on artist top-tracks (where the album varies), null on an album page.

format_type string | null
codec string | null
bit_depth integer | null · int32
sample_rate integer | null · int32
dsd_multiplier integer | null · int32
delivery_codec string | null
is_lossless boolean | null
more array required

Up to four more records by the artist.

each item
object
name string required
plays integer · int64 required
year integer | null · int32
artwork_url string | null

A cover, from one of the listens counted.

caa_id integer | null · int64
caa_release_mbid string | null
400

That entity is not an album.

application/json

object

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.

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

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."
}
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

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

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.

Parameters

id path required

The album's entity id.

integer · int64

Responses

200

OK.

application/json

object

The record as MusicBrainz has it, including tracks you have never played. Nothing is stored.

source string required

Always musicbrainz.

mbid_release string | null
title string required
artist string required
year integer | null · int64
format string | null
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.

title string required
artist string | null
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

Mirrors the HTTP status.

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

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."
}
404

No matching release, or no such album.

application/json

object

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.

502

MusicBrainz could not be reached.

application/json

object

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.

GET /api/v1/artists/{id} #
Token reador Session

An artist page.

Scope: read (since v0.120). Same shape and same rules as the album page.

Parameters

id path required

The artist's entity id, from GET /api/v1/resolve.

integer · int64

Responses

200

OK.

application/json

object

An artist page: your listening of one artist.

id integer · int64 required

The entity id.

kind string required

Always artist.

name string required
mbid string | null
image_url string | null

The portrait the charts use.

landscape_url string | null

The wide image, only where you chose one.

artist_row_id integer | null · int64

The shared artists row the image endpoints address.

rank integer · int64 required

Where this artist sits among yours by plays, 1 being the most played.

artist_total integer · int64 required

How many artists that rank is out of.

plays integer · int64 required
album_count integer · int64 required
track_count integer · int64 required
loved boolean required
bio object required

A description of the album or artist, with the attribution it must be shown with. Every field is null when no provider had one.

text string | null
attribution string | null

Shown with the text, always — Last.fm's is CC BY-SA, and one without the other breaks the licence.

provider string | null
url string | null

Where the full text lives.

stats array required

Four cards: plays, hours, first heard, and this artist's share of all your listening.

each item
object

One stat card, phrased by the server so every surface says "412 plays" the same way.

k string required

The label — Your Plays, Hours, First Heard, Share.

v string required

The figure, formatted.

sub string required

The line under it.

tone string required

plain or dark.

albums array required

Up to six records.

each item
object
name string required
plays integer · int64 required
year integer | null · int32
artwork_url string | null

A cover, from one of the listens counted.

caa_id integer | null · int64
caa_release_mbid string | null
top_tracks array required

The ten most-played tracks.

each item
object

One distinct track. The quality columns are carried raw so the frontend's existing qualityLabel() produces the same tag it does on a history row — duplicating that logic server-side would let the two drift.

title string required
plays integer · int64 required
duration integer | null · int64
track_number integer | null · int32
album string | null

Set on artist top-tracks (where the album varies), null on an album page.

format_type string | null
codec string | null
bit_depth integer | null · int32
sample_rate integer | null · int32
dsd_multiplier integer | null · int32
delivery_codec string | null
is_lossless boolean | null
trend array required
each item
object
label string required
value integer · int64 required
400

That entity is not an artist.

application/json

object

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.

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

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."
}
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

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

GET /api/v1/resolve #
Token writeor Session

Resolve names to an entity id.

Scope: write (since v0.120), although it is a GET, because it creates the entity on first visit — a write on a read path, on purpose. The browser knows names, not ids, so without this every link out of the history would be dead until you happened to annotate the thing it points at. POST /api/v1/notes/preview takes write for the same reason.

Parameters

kind query required

album / release, artist, or track / recording.

string
name query required
string
artist query

Required for an album or a track: the same title by two artists is two different records.

string

Responses

200

OK.

application/json

object

Name → entity id, creating the entity if this is the first time anyone has pointed at it. Lets a history row link to /album/<id> without the browser knowing any ids. An entity, found or created.

id integer · int64 required
kind string required

The stored kind: release, artist or recording.

400

Malformed or rejected input.

application/json

object

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.

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

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."
}
GET /api/v1/art/{id} #
Token reador Session

Cover art for a listen, proxied.

Scope: read (since v0.120). Proxies the user's own library server, which the browser usually cannot reach and must not hold credentials for.

Parameters

id path required

The listen.

integer · int64

Responses

200

Image bytes, Cache-Control: private for a week.

image/*

string · binary
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

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."
}
404

No artwork. The client falls back to a lettered tile.

application/json

object

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.

GET /api/v1/artists/img/{id} #
Token reador Session

An artist portrait, proxied.

Scope: read (since v0.120).

Parameters

id path required

The artists row — artist_row_id on an artist page.

integer · int64

Responses

200

Image bytes, Cache-Control: private for a week.

image/*

string · binary
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

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."
}
404

No portrait yet. Retried later — the answer changes when the artist is added to a library.

application/json

object

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.

PUT /api/v1/artists/img/{id} #
Session

Override an artist portrait.

Session-only. A manual override beats every provider — this is a journal, and whoever keeps it gets the last word.

Parameters

id path required

The artists row.

integer · int64

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 portrait, set or cleared. Null when cleared.

image_url string | null
400

Malformed or rejected input.

application/json

object

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.

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

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."
}
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

Mirrors the HTTP status.

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.

Parameters

id path required

The artists row.

integer · int64

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

Mirrors the HTTP status.

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

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."
}
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

Mirrors the HTTP status.

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.

Parameters

id path required

The album's entity id.

integer · int64

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
400

Not a URL.

application/json

object

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.

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

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."
}
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

Mirrors the HTTP status.

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.

string

Responses

200

Image bytes, Cache-Control: private, immutable.

image/*

string · binary
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

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."
}
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

Mirrors the HTTP status.

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.

Parameters

id path required

The artists row.

integer · int64

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.

image_url string | null
400

Malformed or rejected input.

application/json

object

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.

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

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."
}
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

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

413

Larger than 8 MB.

application/json

object

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.

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

Mirrors the HTTP status.

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.

Parameters

id path required

The artists row.

integer · int64

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

Mirrors the HTTP status.

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

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."
}
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

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

413

Larger than 8 MB.

application/json

object

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.

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

Mirrors the HTTP status.

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.

Parameters

id path required

The album's entity id.

integer · int64

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

Mirrors the HTTP status.

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

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."
}
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

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

413

Larger than 8 MB.

application/json

object

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.

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

Mirrors the HTTP status.

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.

Parameters

artist query required

As you would submit it.

string
album query required

As you would submit it.

string

Responses

200

Whether a cover is already held.

application/json

object

Whether the album already has a cover.

held boolean required
400

A blank artist or album.

application/json

object

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.

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

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."
}
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

Mirrors the HTTP status.

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

200

Stored, or already held.

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

Always true.

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

Mirrors the HTTP status.

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

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."
}
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

Mirrors the HTTP status.

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"
}
413

Larger than 8 MB.

application/json

object

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.

415

Not an image type this server stores.

application/json

object

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.

GET /api/v1/entities/{id} #
Session or Token read

One entity.

Scope: read (since v0.39). Needed to render a note's target.

Parameters

id path required

The entity.

integer · int64

Responses

200

OK.

application/json

object

One entity, and your note on it.

entity object required

Something a note or a love can point at: a recording, a release, an artist.

id integer · int64 required
kind string required

recording, release or artist.

name string required
artist_name string required

Empty for kind = 'artist', where the name is the artist.

mbid string | null
created_at integer · int64 required
loved boolean required
note object | null

A liner note, with everything a screen needs: what it hangs off, the rendered body, whether the subject is loved, and how many earlier versions exist. Exactly one of entity, listen, period and line is set.

id integer · int64 required
body_md string required

Markdown, with [[kind:name]] shortcodes.

excerpt string required

The first 140 characters, as text.

visibility string required

private or another of the allowed visibilities.

created_at integer · int64 required
updated_at integer · int64 required
entity object | null

Something a note or a love can point at: a recording, a release, an artist.

id integer · int64 required
kind string required

recording, release or artist.

name string required
artist_name string required

Empty for kind = 'artist', where the name is the artist.

mbid string | null
created_at integer · int64 required
listen object | null

A listen a note hangs off.

id integer · int64 required
title string required
artist string required
album string | null
timestamp integer · int64 required
period object | null

A period a note hangs off.

unit string required

week, month or year.

key string required

2026-W32, 2026-08, 2026.

line object | null

Set on a pin: the line it hangs off.

artist string required
title string required
line integer required

1-based, blank lines included.

text string | null

The words, when this instance still holds the lyric. A pin outlives the lyric it points at, so null is not an error.

lines_quoted integer required

How many distinct lyric lines the body quotes.

pins array required

The pins that roll up under this note as footnotes, in the order the record plays. Empty in a listing.

each item
object

A pin, as it reads under the note it rolls up into.

note_id integer · int64 required
line object required

One numbered line of a lyric.

artist string required
title string required
line integer required

1-based, blank lines included.

text string | null

The words, when this instance still holds the lyric. A pin outlives the lyric it points at, so null is not an error.

body_md string required
excerpt string required

The first 200 characters, as text.

updated_at integer · int64 required
loved boolean required
revisions integer required

Earlier versions kept.

body_html string | null

The rendered, sanitised body with every shortcode resolved. Absent in a listing.

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

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."
}
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

Mirrors the HTTP status.

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"
}
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

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.