glanceapp/glance · error

%d %s

Error message

%d %s

What it means

Returned by the custom API response handler when the HTTP status is outside 200-299 and the body is not valid JSON: fmt.Errorf("%d %s", resp.StatusCode, http.StatusText(resp.StatusCode)) yields messages like "502 Bad Gateway" or "401 Unauthorized". On 2xx statuses invalid JSON produces a different error ("invalid response JSON"), so this format specifically means non-2xx + non-JSON body.

Solutions

  1. curl the exact URL with the same headers from the widget config and inspect the status code and body.
  2. Fix auth: add/refresh the API key or token header the endpoint expects.
  3. Correct the URL/path (404) or scheme/host (502).
  4. If the endpoint legitimately returns non-JSON error bodies on failure, consider options that tolerate it (e.g. skip JSON validation) or accept the widget showing the error status.
Defensive patterns

Strategy: try-catch

Try / catch

data, err := executeCustomAPIRequest(req)
if err != nil {
    if strings.Contains(err.Error(), "invalid response JSON") {
        // 2xx but bad JSON: log body sample, likely API contract change
    } else {
        // "NNN Status Text": HTTP-level failure — auth, proxy, or wrong URL
    }
    return nil, err
}

Prevention

When it happens

Trigger: Any custom-api request (primary or subrequest) whose endpoint returns e.g. 502/503 from a reverse proxy HTML error page, 401/403 from an auth wall returning HTML, or 404 for a wrong URL path.

Common situations: Expired API key so the gateway returns an HTML login page; wrong URL or path segment; upstream service down and the proxy's error page is HTML; Cloudflare challenge pages.

Related errors


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

Appendix: source

Thrown at internal/glance/widget-custom-api.go:268

	bodyBytes, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	body := strings.TrimSpace(string(bodyBytes))

	if !req.SkipJSONValidation && body != "" && !gjson.Valid(body) {
		if 200 <= resp.StatusCode && resp.StatusCode < 300 {
			truncatedBody, isTruncated := limitStringLength(body, 100)
			if isTruncated {
				truncatedBody += "... <truncated>"
			}

			slog.Error("Invalid response JSON in custom API widget", "url", req.httpRequest.URL.String(), "body", truncatedBody)
			return nil, errors.New("invalid response JSON")
		}

		return nil, fmt.Errorf("%d %s", resp.StatusCode, http.StatusText(resp.StatusCode))

	}

	return &customAPIResponseData{
		JSON:     decoratedGJSONResult{gjson.Parse(body)},
		Response: resp,
	}, nil
}

func fetchAndRenderCustomAPIRequest(
	primaryReq *CustomAPIRequest,
	subReqs map[string]*CustomAPIRequest,
	options customAPIOptions,
	tmpl *template.Template,
) (template.HTML, error) {
	var primaryData *customAPIResponseData
	subData := make(map[string]*customAPIResponseData, len(subReqs))
	var err error

View on GitHub (pinned to 91324e8de7)