{"record":{"id":"acf8bebe88d212c0","repo":"gethomepage/homepage","slug":"error-getting-data-from-audiobookshelf-status","errorCode":null,"errorMessage":"Error getting data from Audiobookshelf: ${status}. Data: ${data.toString()}","messagePattern":"Error getting data from Audiobookshelf: (.+?)\\. Data: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/widgets/audiobookshelf/proxy.js","lineNumber":19,"sourceCode":"import getServiceWidget from \"utils/config/service-helpers\";\nimport createLogger from \"utils/logger\";\nimport { formatApiCall } from \"utils/proxy/api-helpers\";\nimport { httpProxy } from \"utils/proxy/http\";\nimport widgets from \"widgets/widgets\";\n\nconst proxyName = \"audiobookshelfProxyHandler\";\nconst logger = createLogger(proxyName);\n\nasync function retrieveFromAPI(url, key) {\n  const headers = {\n    \"content-type\": \"application/json\",\n    Authorization: `Bearer ${key}`,\n  };\n\n  const [status, , data] = await httpProxy(url, { headers });\n\n  if (status !== 200) {\n    throw new Error(`Error getting data from Audiobookshelf: ${status}. Data: ${data.toString()}`);\n  }\n\n  return JSON.parse(Buffer.from(data).toString());\n}\n\nexport default async function audiobookshelfProxyHandler(req, res) {\n  const { group, service, endpoint, index } = req.query;\n\n  if (!group || !service) {\n    logger.debug(\"Invalid or missing service '%s' or group '%s'\", service, group);\n    return res.status(400).json({ error: \"Invalid proxy service type\" });\n  }\n\n  const widget = await getServiceWidget(group, service, index);\n\n  if (!widget) {\n    logger.debug(\"Invalid or missing widget for service '%s' in group '%s'\", service, group);\n    return res.status(400).json({ error: \"Invalid proxy service type\" });","sourceCodeStart":1,"sourceCodeEnd":37,"githubUrl":"https://github.com/gethomepage/homepage/blob/b6dca1ae033e613d8e692f9a161a3cc53a5a2857/src/widgets/audiobookshelf/proxy.js#L1-L37","documentation":"Thrown by retrieveFromAPI in the Audiobookshelf proxy when the upstream Audiobookshelf HTTP API returns any non-200 status. The proxy forwards credentials as a Bearer token and expects a 200 with JSON; any other status (auth failure, network error reflected as a 4xx/5xx, or a redirect) is treated as fatal before JSON parsing.","triggerScenarios":"Calling the Audiobookshelf /api/... endpoint with an invalid or expired API key (401/403), pointing the widget at the wrong URL so the path 404s, Audiobookshelf behind a reverse proxy returning 502/503, or a typo'd base URL hitting an unrelated server that does not return 200.","commonSituations":"API key mis-typed or copied with whitespace; base URL configured with a trailing slash or wrong port; Audiobookshelf upgraded and changed an endpoint path; reverse proxy (Traefik/Caddy/Nginx) returning an error page instead of proxying; token lacks permission for the requested endpoint.","solutions":["Verify the Audiobookshelf service URL and that the host is reachable from the Homepage container (curl the same URL with the same token).","Confirm the API key is valid in Audiobookshelf under Settings > Users and has not expired.","Check the reverse proxy in front of Audiobookshelf returns 200 for the same path the widget calls.","Inspect the Homepage debug log; data.toString() in the message often contains the upstream body (HTML error page, JSON error) that pinpoints 401/404/502.","Ensure the URL template in the widget config resolves to the v1 API path Audiobookshelf expects."],"exampleFix":"// before\nif (status !== 200) {\n  throw new Error(`Error getting data from Audiobookshelf: ${status}. Data: ${data.toString()}`);\n}\n// after\nif (status !== 200) {\n  throw new Error(`Audiobookshelf ${url} returned HTTP ${status}: ${Buffer.from(data).toString().slice(0, 200)}`);\n}","handlingStrategy":"retry","validationCode":"// Pre-flight the Audiobookshelf URL and key before the widget polls.\nasync function checkAudiobookshelf(base, key) {\n  const res = await fetch(`${base.replace(/\\/$/, \"\")}/api/items?n=1`, {\n    headers: { Authorization: `Bearer ${key}` },\n  });\n  if (!res.ok) throw new Error(`Audiobookshelf pre-flight HTTP ${res.status}`);\n  return true;\n}","typeGuard":"function isAudiobookshelfErrorPayload(v) {\n  return typeof v === \"object\" && v !== null && typeof v.error === \"string\";\n}","tryCatchPattern":"try {\n  const data = await retrieveFromAPI(url, key);\n  res.json(data);\n} catch (err) {\n  logger.warn(\"Audiobookshelf proxy failed for %s: %s\", url, err.message);\n  res.status(502).json({ error: \"Audiobookshelf unavailable\", detail: err.message });\n}","preventionTips":["Store the Audiobookshelf API key in a secret manager and inject via env var to avoid copy/whitespace errors.","Add a health-check dependency so Homepage only calls Audiobookshelf when it is reachable.","Wrap the proxy call so a 5xx upstream becomes a 502 to the dashboard, not a crash."],"tags":["network","audiobookshelf","http-proxy","authentication","widget"],"backgroundTag":null,"analyzedSha":"b6dca1ae033e613d8e692f9a161a3cc53a5a2857","analyzedAt":"2026-08-13T04:48:44.121Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}