{"record":{"id":"d5f25d8a10f578cc","repo":"glanceapp/glance","slug":"missing-api-token","errorCode":null,"errorMessage":"missing API token","messagePattern":"missing API token","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/glance/widget-dns-stats.go","lineNumber":316,"sourceCode":"\nfunc (p *pihole5TopBlockedDomains) UnmarshalJSON(data []byte) error {\n\t// NOTE: do not change to piholeTopBlockedDomains type here or it will cause a stack overflow\n\t// because of the UnmarshalJSON method getting called recursively\n\ttemp := make(map[string]int)\n\n\terr := json.Unmarshal(data, &temp)\n\tif err != nil {\n\t\t*p = make(pihole5TopBlockedDomains)\n\t} else {\n\t\t*p = temp\n\t}\n\n\treturn nil\n}\n\nfunc fetchPihole5Stats(instanceURL string, allowInsecure bool, token string, noGraph bool) (*dnsStats, error) {\n\tif token == \"\" {\n\t\treturn nil, errors.New(\"missing API token\")\n\t}\n\n\trequestURL := strings.TrimRight(instanceURL, \"/\") +\n\t\t\"/admin/api.php?summaryRaw&topItems&overTimeData10mins&auth=\" + token\n\n\trequest, err := http.NewRequest(\"GET\", requestURL, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar client = ternary(allowInsecure, defaultInsecureHTTPClient, defaultHTTPClient)\n\tresponseJson, err := decodeJsonFromRequest[pihole5StatsResponse](client, request)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstats := &dnsStats{\n\t\tTotalQueries:   responseJson.TotalQueries,","sourceCodeStart":298,"sourceCodeEnd":334,"githubUrl":"https://github.com/glanceapp/glance/blob/91324e8de762702e97b0ac5c8e36271d644d8642/internal/glance/widget-dns-stats.go#L298-L334","documentation":"Inside Custom API templates you can build requests with template funcs (withQueryParameter, withStringBody, etc.) and execute them synchronously with getResponse. getResponse first calls req.initialize(), which parses and validates the (possibly template-substituted) URL, method and body; on failure it panics with 'initializing request: %v' and the render aborts with that message.","triggerScenarios":"A dynamically-built request ends up invalid after template substitution: an option-derived URL that is empty or malformed (http.Get on a template placeholder that resolved to nothing), an invalid HTTP method, or an unusable body content type. Example: {{ (getResponse (.URL \"))).Response.Status }} with no url option.","commonSituations":"Templating the URL/host from options where the option is missing or resolves to an empty string; constructing URLs by string concatenation that yields a scheme-less result; passing a computed method string with whitespace/newlines from YAML.","solutions":["Inspect the rendered request: log the URL/method/body in the template ({{ .Log }} or render them into the output) to see what actually got substituted","Ensure any option used in the request URL is defined in options: and non-empty before calling getResponse","Build URLs with proper helpers instead of raw concatenation, so schemes/escaping stay valid","Guard in the template: {{ if .options-derived-value }}{{ getResponse ... }}{{ end }}"],"exampleFix":"// before\n{{ $r := (getResponses (withURL .StringOr \"baseurl\" \"\")).Response }}\n// after: only issue the request when the base URL exists\n{{ $base := .StringOr \"baseurl\" \"\" }}\n{{ if $base }}{{ $r := (getResponse (withURL $base)).Response }}{{ end }}","handlingStrategy":"validation","validationCode":"// template-side: only call getResponse with a validated URL\n{{ $u := .StringOr \"baseurl\" \"\" }}\n{{ if and $u (strings.HasPrefix $u \"http\") }}\n  {{ getResponse (withURL $u) }}\n{{ end }}","typeGuard":null,"tryCatchPattern":"Since the panic happens inside template execution, catch it at the Execute call: the template engine returns it as an error ('initializing request: ...'); isolate the failing widget render and show the message in the widget frame.","preventionTips":["Always default-check option-derived URLs with .StringOr and an {{ if }} guard before getResponse","Build requests via the with* helpers rather than string concatenation so URLs stay well-formed","Log the substituted URL/method when debugging — the panic message includes the underlying url.Error"],"tags":["go","custom-api","templates","http","panic"],"backgroundTag":null,"analyzedSha":"91324e8de762702e97b0ac5c8e36271d644d8642","analyzedAt":"2026-08-15T14:12:54.279Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}