VictoriaMetrics/VictoriaMetrics · error
failed parse digitalocean api response: %q, err: %w
Error message
failed parse digitalocean api response: %q, err: %w
What it means
parseAPIResponse unmarshals the raw body of a DigitalOcean /v2/droplets page into listDropletResponse. This error includes the raw data and the json error, thrown when the response is not the expected JSON shape {"droplets":[...],"links":{...}}.
Source
Thrown at lib/promscrape/discovery/digitalocean/api.go:88
return nil, fmt.Errorf("cannot fetch data from digitalocean list api: %w", err)
}
apiResp, err := parseAPIResponse(data)
if err != nil {
return nil, err
}
droplets = append(droplets, apiResp.Droplets...)
nextAPIURL, err = apiResp.nextURLPath()
if err != nil {
return nil, err
}
}
return droplets, nil
}
func parseAPIResponse(data []byte) (*listDropletResponse, error) {
var dps listDropletResponse
if err := json.Unmarshal(data, &dps); err != nil {
return nil, fmt.Errorf("failed parse digitalocean api response: %q, err: %w", data, err)
}
return &dps, nil
}
View on GitHub (pinned to 5079fb58f1)
Solutions
- Inspect the data in the error message to see the actual payload
- Confirm the endpoint really is api.digitalocean.com and returns droplets JSON
- Check for proxies injecting HTML (auth pages, block pages) and bypass/authorize them
- Verify the API token is valid — some auth failures come back as non-droplet JSON
- Re-run with a single curl to compare the raw response with the expected structure
Example fix
# before
curl -s https://api.digitalocean.com/v2/droplets
<html>You must be logged in...</html>
# after (with valid token)
curl -s -H "Authorization: Bearer $DO_TOKEN" https://api.digitalocean.com/v2/droplets
{"droplets":[...],"links":{},"meta":{"total":3}} Defensive patterns
Strategy: try-catch
Validate before calling
# Sanity-check the endpoint returns droplet JSON before wiring discovery:
curl -fsS -H "Authorization: Bearer $DO_TOKEN" https://api.digitalocean.com/v2/droplets?per_page=1 | jq 'has("droplets")'
# Expected output: true Try / catch
data, err := getDroplets(client.GetAPIResponse)
if err != nil {
if strings.Contains(err.Error(), "failed parse digitalocean api response") {
// the raw payload is embedded in err; log it and skip this refresh cycle
return nil, err // vmagent will retry next refresh
}
return nil, err
} Prevention
- Check for intercepting proxies / captive portals on the API path
- Verify token validity — some auth failures return unexpected payloads
- Pin sd_config `server` to the genuine DigitalOcean API
- Watch for API format changes after DigitalOcean announcements
When it happens
Trigger: The API (or an intermediary) returned non-JSON content: an HTML error/login page, a plain-text rate-limit message, empty body, or a JSON body whose top-level structure changed.
Common situations: Intercepting proxies or captive portals returning HTML; DigitalOcean API returning a JSON error object without a 'droplets' key and incompatible types elsewhere; custom 'server' pointing to a non-DigitalOcean endpoint; truncated responses on flaky connections.
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
- cannot parse instance list from %q: %w
- cannot unmarshal NamespaceList: %w
- unexpected empty json
- unexpected non json object {} type: %q
- unexpected non json object; type: %q
AI-assisted analysis of VictoriaMetrics/VictoriaMetrics@5079fb58f1 (2026-09-03).
Data as JSON: /api/errors/63a5b20a9b3d58cc.
Report an issue: GitHub.