Back to blog
FILE 0xA3·LIDARR RETURNS HTTP 500 ON PUT /ALBUM/MONITOR MID-ADD

Lidarr returns HTTP 500 on PUT /album/monitor mid-add

May 3, 2026 · homelab, lidarr, api

A note for anyone scripting Lidarr through its API: there's a race in the "add an artist with monitored albums" flow that surfaces as a 500 on the album-monitor endpoint. It's not a bug in your code.

What was happening

The flow I was running:

# 1. add artist
r = post("/api/v1/artist", json={"artistName": ..., "monitored": True, ...})
artist_id = r.json()["id"]

# 2. fetch album list for new artist
albums = get(f"/api/v1/album?artistId={artist_id}").json()

# 3. set monitor flags on each album
put("/api/v1/album/monitor",
    json={"albumIds": [a["id"] for a in albums], "monitored": True})

Step 3 returned a 500 about a third of the time. No useful body. Lidarr logs showed an internal exception about the album records being mutated mid-write.

What I found

When you add an artist with monitored=True, Lidarr kicks off a background refresh that pulls the discography from MusicBrainz and writes album records. That refresh isn't done by the time the initial POST /artist call returns. If you immediately fetch the album list and then immediately try to flip monitor flags on those albums, you're competing with the background refresh for the same database rows.

The album IDs you fetched in step 2 might be valid, partially-written, or already replaced by the time step 3's transaction commits. Lidarr's monitor endpoint takes a write lock and fails when the row it's trying to update has been touched by the refresh in the interim.

The fix

Wait for the refresh command to actually finish before touching the albums. Lidarr exposes a command status endpoint:

def wait_for_artist_refresh(artist_id, timeout=120):
    deadline = time.time() + timeout
    while time.time() < deadline:
        cmds = get("/api/v1/command").json()
        active = [c for c in cmds
                  if c["name"] == "RefreshArtist"
                  and c.get("body", {}).get("artistId") == artist_id
                  and c["status"] in ("queued", "started")]
        if not active:
            return
        time.sleep(2)
    raise TimeoutError(f"artist {artist_id} refresh did not finish")

post("/api/v1/artist", json={...})
wait_for_artist_refresh(artist_id)
put("/api/v1/album/monitor", json={...})

The 500s went away once I wired that wait in. Most artist refreshes complete inside 10 seconds; a few stragglers (artists with massive discographies) take a minute. The two-second poll interval is conservative enough that I'm not hammering the command endpoint.

What I'd do differently

API contracts that involve a background refresh need to be documented as such. Lidarr's API docs imply POST /artist is synchronous because the response includes the artist object, but the "ready to operate on" state is several seconds later. Whenever I'm scripting against an arr app — or any app where mutations trigger background work — I now assume the next-step API call needs a "wait for related commands to finish" guard, and only remove it if I can prove otherwise.