glanceapp/glance · error

could not fetch list of watch UUIDs: %v

Error message

could not fetch list of watch UUIDs: %v

What it means

Returned by fetchWatchUUIDsFromChangeDetection in internal/glance/widget-changedetection.go when the HTTP GET to {instanceURL}/api/v1/watch fails or its body cannot be decoded into a map[string]struct{} via decodeJsonFromRequest. This wraps transport errors, non-2xx responses, and JSON decode failures into a single message.

Source

Thrown at internal/glance/widget-changedetection.go:107

type changeDetectionResponseJson struct {
	Title        string `json:"title"`
	URL          string `json:"url"`
	LastChanged  int64  `json:"last_changed"`
	DateCreated  int64  `json:"date_created"`
	PreviousHash string `json:"previous_md5"`
}

func fetchWatchUUIDsFromChangeDetection(instanceURL string, token string) ([]string, error) {
	request, _ := http.NewRequest("GET", fmt.Sprintf("%s/api/v1/watch", instanceURL), nil)

	if token != "" {
		request.Header.Add("x-api-key", token)
	}

	uuidsMap, err := decodeJsonFromRequest[map[string]struct{}](defaultHTTPClient, request)
	if err != nil {
		return nil, fmt.Errorf("could not fetch list of watch UUIDs: %v", err)
	}

	uuids := make([]string, 0, len(uuidsMap))

	for uuid := range uuidsMap {
		uuids = append(uuids, uuid)
	}

	return uuids, nil
}

func fetchWatchesFromChangeDetection(instanceURL string, requestedWatchIDs []string, token string) (changeDetectionWatchList, error) {
	watches := make(changeDetectionWatchList, 0, len(requestedWatchIDs))

	if len(requestedWatchIDs) == 0 {
		return watches, nil
	}

View on GitHub (pinned to 91324e8de7)

Solutions

  1. curl -H 'x-api-key: TOKEN' {instanceURL}/api/v1/watch and confirm a JSON object keyed by UUID comes back.
  2. Verify the token matches Settings > API in changedetection.io and that the widget key: is set (not empty).
  3. Check the instance URL scheme/host and that the host running glance can reach it (DNS, firewall, TLS).
  4. If a proxy sits in front, ensure it passes the x-api-key header and does not rewrite /api/v1/watch.
Defensive patterns

Strategy: retry

Validate before calling

// before building the widget, probe the API contract
req, _ := http.NewRequest("GET", instanceURL+"/api/v1/watch", nil)
if token != "" {
    req.Header.Add("x-api-key", token)
}
resp, err := defaultHTTPClient.Do(req)
if err != nil || resp.StatusCode != 200 {
    return fmt.Errorf("changedetection API unreachable or unauthorized: %v", err)
}
defer resp.Body.Close()
if ct := resp.Header.Get("Content-Type"); !strings.Contains(ct, "json") {
    return errors.New("changedetection API did not return JSON (check URL/proxy)")
}

Try / catch

uuids, err := fetchWatchUUIDsFromChangeDetection(url, token)
if err != nil {
    // transient network or auth issue: skip this refresh, keep last good render
    slog.Warn("changedetection fetch failed", "error", err)
    return
}

Prevention

When it happens

Trigger: GET {url}/api/v1/watch with optional x-api-key header returns 401/403 (bad token), connection refused/DNS failure (bad URL), a reverse-proxy HTML error page instead of JSON, or a changedetection.io version whose API payload is not a UUID-keyed object.

Common situations: Wrong or missing api token in the widget's token field; instance URL pointing at the UI path instead of the API root; self-hosted instance behind a proxy that intercepts /api; changedetection.io major version change altering the response shape.

Related errors


AI-assisted analysis of glanceapp/glance@91324e8de7 (2026-08-15). Data as JSON: /api/errors/ed5fdde487c9b882. Report an issue: GitHub.