gethomepage/homepage · error · Error

Duplicati request failed for ${endpoint}

Error message

Duplicati request failed for ${endpoint}

What it means

Thrown by apiGet() in the Duplicati proxy when an authenticated GET to a Duplicati API endpoint returns a non-200 status. This fires after a successful login, so the token is valid but the specific data endpoint still rejected the request.

Source

Thrown at src/widgets/duplicati/proxy.js:83

  const body = asJson(data);
  if (!body?.AccessToken) {
    throw new Error("Duplicati login response did not include an access token");
  }

  return body.AccessToken;
}

async function apiGet(widget, endpoint, accessToken) {
  const url = new URL(formatApiCall(widgets[widget.type].api, { endpoint, ...widget }));
  const [status, , data] = await httpProxy(url, {
    method: "GET",
    headers: {
      Authorization: `Bearer ${accessToken}`,
    },
  });

  if (status !== 200) {
    throw new Error(`Duplicati request failed for ${endpoint}`);
  }

  return asJson(data);
}

export default async function duplicatiProxyHandler(req, res) {
  const { group, service, index } = req.query;
  const widget = await getServiceWidget(group, service, index);

  if (!widget) {
    return res.status(400).json({ error: "Invalid proxy service type" });
  }

  if (!widget.url || !widget.password) {
    return res.status(500).json({
      error: {
        message: `Duplicati widget is missing required url and password`,
      },

View on GitHub (pinned to b6dca1ae03)

Solutions

  1. Check the Homepage debug log for the status code tied to the failing endpoint to distinguish 404 (wrong path) from 401 (token) from 500 (server).
  2. Confirm the endpoint name matches Duplicati's API for the installed version.
  3. If intermittent, treat 500/429 as transient and verify Duplicati health / proxy rate limits.
  4. Re-check that login (which produced the token) used the same base URL as this GET so the token is scoped correctly.
  5. If 401, review whether the token TTL is shorter than the polling interval and whether login should be re-run per request.

Example fix

// before
if (status !== 200) {
  throw new Error(`Duplicati request failed for ${endpoint}`);
}
// after
if (status !== 200) {
  throw new Error(`Duplicati GET ${endpoint} failed (HTTP ${status}): ${Buffer.from(data).toString().slice(0, 200)}`);
}
Defensive patterns

Strategy: retry

Validate before calling

// Validate the endpoint against the Duplicati API surface before calling.
const DUPLICATI_ENDPOINTS = new Set(["backup","state","progress","serverstate"]);
function isValidDuplicatiEndpoint(ep) {
  return typeof ep === "string" && DUPLICATI_ENDPOINTS.has(ep.toLowerCase());
}

Try / catch

try {
  return await apiGet(widget, endpoint, accessToken);
} catch (err) {
  if (/request failed for/.test(err.message) && isTransient) {
    // Re-login once, then retry the GET a single time.
    const token = await login(widget);
    return await apiGet(widget, endpoint, token);
  }
  throw err;
}

Prevention

When it happens

Trigger: The AccessToken used for the call is valid but the requested endpoint path is wrong (404), the token expired between login and this call (401), Duplicati returns 500 for a specific backup task, or the endpoint name passed in the widget does not exist on this Duplicati version.

Common situations: Endpoint parameter in the widget config mis-spelled or not supported by the installed Duplicati version; clock skew causing immediate token expiry; Duplicati mid-backup returning 500 for status endpoints; reverse proxy rate-limiting returning 429.

Related errors


AI-assisted analysis of gethomepage/homepage@b6dca1ae03 (2026-08-13). Data as JSON: /api/errors/bc88577b9b4dabbb. Report an issue: GitHub.