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

  1. Inspect the data in the error message to see the actual payload
  2. Confirm the endpoint really is api.digitalocean.com and returns droplets JSON
  3. Check for proxies injecting HTML (auth pages, block pages) and bypass/authorize them
  4. Verify the API token is valid — some auth failures come back as non-droplet JSON
  5. 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

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


AI-assisted analysis of VictoriaMetrics/VictoriaMetrics@5079fb58f1 (2026-09-03). Data as JSON: /api/errors/63a5b20a9b3d58cc. Report an issue: GitHub.