wtfutil/wtf · error
failed to retrieve top items: check provided api URL and to
Error message
failed to retrieve top items: check provided api URL and token %s
What it means
getTopItems in modules/pihole/client.go wraps a json.Unmarshal failure while decoding the topItems response body into the TopItems struct. The library throws it because the server returned a body that is not JSON of the expected shape (note the code also ignores io.ReadAll's error here).
Source
Thrown at modules/pihole/client.go:160
return ti, fmt.Errorf(" failed to connect to Pi-hole server\n %s", parseError(err))
}
defer func() {
if closeErr := resp.Body.Close(); closeErr != nil {
return
}
}()
if resp.StatusCode >= http.StatusBadRequest {
return ti, fmt.Errorf(" failed to retrieve version from Pi-hole server\n http status code: %d",
resp.StatusCode)
}
var rBody []byte
rBody, err = io.ReadAll(resp.Body)
if err = json.Unmarshal(rBody, &ti); err != nil {
return ti, fmt.Errorf(" failed to retrieve top items: check provided api URL and token\n %s",
parseError(err))
}
return ti, err
}
type TopClients struct {
TopSources map[string]int `json:"top_sources"`
}
// parseError removes any token from output and ensures a non-nil response
func parseError(err error) string {
if err == nil {
return "unknown error"
}
var re = regexp.MustCompile(`auth=[a-zA-Z0-9]*`)
View on GitHub (pinned to bb838c1ccb)
Solutions
- Curl the exact request URL (with auth and topItems params) and inspect the raw body to see what is actually returned.
- Fix settings.token so Pi-hole returns real topItems data instead of an auth error.
- Check your Pi-hole version; for v6, update the client/structs to match the new API schema or use a compatible client version.
- Remove any proxy that injects HTML pages into API responses.
Example fix
// before
settings.token = "" // auth error JSON returned
// after
settings.token = os.Getenv("PIHOLE_WEBPASSWORD") Defensive patterns
Strategy: try-catch
Validate before calling
resp, err := http.Get(settings.apiUrl + "?auth=" + settings.token + "&topItems=10")
if err == nil {
body, _ := io.ReadAll(resp.Body)
var probe map[string]json.RawMessage
if json.Unmarshal(body, &probe) != nil {
return errors.New("topItems endpoint did not return expected JSON")
}
} Try / catch
if err := client.GetTopItemsView(ctx); err != nil {
if strings.Contains(err.Error(), "failed to retrieve top items") {
log.Printf("topItems response not parseable — check token and Pi-hole version: %v", err)
return
}
return err
} Prevention
- Curl the full topItems request and confirm the JSON schema matches the client's TopItems struct.
- Keep the client library version aligned with your Pi-hole major version.
- Verify the token is valid so the API returns data, not an auth-error payload.
- Ensure no proxy injects HTML into API responses.
When it happens
Trigger: In getTopItems (via getTopItemsView), json.Unmarshal(rBody, &ti) fails: response is HTML, empty, an auth-error JSON object, or a Pi-hole API version whose topItems schema differs from the TopItems struct.
Common situations: Invalid auth token returning an auth error payload; Pi-hole v6 API returning a different JSON schema than this legacy client expects; an HTML error page from a reverse proxy; empty body due to API parameter mismatch.
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
- failed to retrieve status: check provided api URL and token
- failed to retrieve top clients: check provided api URL and
- failed to parse query types response %s
- decoding response: %w
- failed to marshal request: %v
AI-assisted analysis of wtfutil/wtf@bb838c1ccb (2026-09-03).
Data as JSON: /api/errors/f6e2429119af2b24.
Report an issue: GitHub.