{"record":{"id":"63a5b20a9b3d58cc","repo":"VictoriaMetrics/VictoriaMetrics","slug":"failed-parse-digitalocean-api-response-q-err","errorCode":null,"errorMessage":"failed parse digitalocean api response: %q, err: %w","messagePattern":"failed parse digitalocean api response: %q, err: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"lib/promscrape/discovery/digitalocean/api.go","lineNumber":88,"sourceCode":"\t\t\treturn nil, fmt.Errorf(\"cannot fetch data from digitalocean list api: %w\", err)\n\t\t}\n\t\tapiResp, err := parseAPIResponse(data)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdroplets = append(droplets, apiResp.Droplets...)\n\t\tnextAPIURL, err = apiResp.nextURLPath()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn droplets, nil\n}\n\nfunc parseAPIResponse(data []byte) (*listDropletResponse, error) {\n\tvar dps listDropletResponse\n\tif err := json.Unmarshal(data, &dps); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed parse digitalocean api response: %q, err: %w\", data, err)\n\t}\n\treturn &dps, nil\n}\n","sourceCodeStart":70,"sourceCodeEnd":92,"githubUrl":"https://github.com/VictoriaMetrics/VictoriaMetrics/blob/5079fb58f1e8e62113f90c945ad71586c797d770/lib/promscrape/discovery/digitalocean/api.go#L70-L92","documentation":"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\":{...}}.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\ncurl -s https://api.digitalocean.com/v2/droplets\n<html>You must be logged in...</html>\n\n# after (with valid token)\ncurl -s -H \"Authorization: Bearer $DO_TOKEN\" https://api.digitalocean.com/v2/droplets\n{\"droplets\":[...],\"links\":{},\"meta\":{\"total\":3}}","handlingStrategy":"try-catch","validationCode":"# Sanity-check the endpoint returns droplet JSON before wiring discovery:\ncurl -fsS -H \"Authorization: Bearer $DO_TOKEN\" https://api.digitalocean.com/v2/droplets?per_page=1 | jq 'has(\"droplets\")'\n# Expected output: true","typeGuard":null,"tryCatchPattern":"data, err := getDroplets(client.GetAPIResponse)\nif err != nil {\n    if strings.Contains(err.Error(), \"failed parse digitalocean api response\") {\n        // the raw payload is embedded in err; log it and skip this refresh cycle\n        return nil, err // vmagent will retry next refresh\n    }\n    return nil, err\n}","preventionTips":["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"],"tags":["digitalocean","json","api","parsing"],"backgroundTag":"json-unmarshal-failed","analyzedSha":"5079fb58f1e8e62113f90c945ad71586c797d770","analyzedAt":"2026-09-03T18:10:26.153Z","contentChangedAt":"2026-09-03T18:10:26.153Z","schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}