wtfutil/wtf · error

failed to retrieve top clients: check provided api URL and

Error message

 failed to retrieve top clients: check provided api URL and token
 %s

What it means

After reading the response, getTopClients unmarshals the body into TopClients{TopSources map[string]int}. This error is thrown when the body is not valid JSON or does not match that shape. The library deliberately hints that the API URL or token is wrong, because invalid auth usually makes Pi-hole return an HTML error page or a different JSON structure.

Source

Thrown at modules/pihole/client.go:229

	defer func() {
		if closeErr := resp.Body.Close(); closeErr != nil {
			return
		}
	}()

	if resp.StatusCode >= http.StatusBadRequest {
		return tc, fmt.Errorf(" failed to retrieve version from Pi-hole server\n http status code: %d",
			resp.StatusCode)
	}

	var rBody []byte

	if rBody, err = io.ReadAll(resp.Body); err != nil {
		return tc, fmt.Errorf(" failed to read top clients response\n %s", parseError(err))
	}

	if err = json.Unmarshal(rBody, &tc); err != nil {
		return tc, fmt.Errorf(" failed to retrieve top clients: check provided api URL and token\n %s",
			parseError(err))
	}

	return tc, err
}

type QueryTypes struct {
	QueryTypes map[string]float32 `json:"querytypes"`
}

func getQueryTypes(c http.Client, settings *Settings) (qt QueryTypes, err error) {
	var req *http.Request

	var url *url2.URL

	if url, err = url2.Parse(settings.apiUrl); err != nil {
		return qt, fmt.Errorf(" failed to parse API URL\n %s", parseError(err))
	}

View on GitHub (pinned to bb838c1ccb)

Solutions

  1. Print/curl the raw response body to see what Pi-hole actually returned
  2. Verify the API token is current (Settings > API in the Pi-hole admin UI) and passed as settings.token
  3. Confirm apiUrl ends at /admin/api.php (the web UI page will return HTML, not JSON)
  4. If running Pi-hole v6+, check whether the response schema still contains top_sources; upgrade or adapt the client struct if not
  5. If the token itself appears fine, inspect whether top_sources changed type (e.g. values as floats) and adjust the TopClients struct

Example fix

// before: struct may not match v6 schema
type TopClients struct {
    TopSources map[string]int `json:"top_sources"`
}

// after: tolerate float values from newer API
type TopClients struct {
    TopSources map[string]float64 `json:"top_sources"`
}
Defensive patterns

Strategy: validation

Validate before calling

// pre-flight: confirm the endpoint returns JSON with top_sources
resp, _ := http.Get(settings.apiUrl + "auth=" + settings.token + "&topClients=1")
b, _ := io.ReadAll(resp.Body)
if !json.Valid(b) || !bytes.Contains(b, []byte("top_sources")) {
    return fmt.Errorf("unexpected pihole response; check token and api.php URL: %.100s", b)
}

Type guard

func looksLikeTopClients(b []byte) bool {
    var probe struct {
        TopSources map[string]int `json:"top_sources"`
    }
    return json.Unmarshal(b, &probe) == nil
}

Try / catch

tc, err := getTopClients(client, settings)
if err != nil {
    if strings.Contains(err.Error(), "check provided api URL and token") {
        // body wasn't decodable: dump sanitized body and re-verify token/URL
        log.Printf("non-JSON response from %s; verify token", redactedURL)
    }
    return err
}

Prevention

When it happens

Trigger: json.Unmarshal fails on the topClients response: Pi-hole returns HTML (login page or error page) instead of JSON, an empty body, or JSON without a top_sources object because the auth token was rejected.

Common situations: Wrong/expired API token causing an HTML response, apiUrl pointing at the web UI instead of api.php, Pi-hole v6 changed the response schema (top_sources shape differs), or a proxy returning an error page.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


AI-assisted analysis of wtfutil/wtf@bb838c1ccb (2026-09-03). Data as JSON: /api/errors/2e4ff87bee252707. Report an issue: GitHub.