wtfutil/wtf · error

unexpected status %d from DEV.to API

Error message

unexpected status %d from DEV.to API

What it means

FetchArticles in modules/devto/client.go only accepts HTTP 200 from the DEV.to /articles endpoint; any other status (401, 403, 422, 429, 5xx) is rejected with this message. The library does not inspect the body on failure, so the status code is the only diagnostic returned. It wraps nothing — the raw status number is interpolated into the message.

Source

Thrown at modules/devto/client.go:74

	}
	if perPage > 0 {
		q.Set("per_page", fmt.Sprintf("%d", perPage))
	}
	u.RawQuery = q.Encode()

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
	if err != nil {
		return nil, fmt.Errorf("creating request: %w", err)
	}

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("executing request: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("unexpected status %d from DEV.to API", resp.StatusCode)
	}

	var articles []Article
	if err := json.NewDecoder(resp.Body).Decode(&articles); err != nil {
		return nil, fmt.Errorf("decoding response: %w", err)
	}

	return articles, nil
}

View on GitHub (pinned to bb838c1ccb)

Solutions

  1. Verify the API key passed to the client is set and valid (curl -H 'api-key: ...' https://dev.to/api/articles/me).
  2. Log the full status code; if 429, back off and increase the widget refresh interval.
  3. If 422, check the query parameters built into the request URL for invalid values.
  4. If 5xx, retry later — the failure is server-side, not a client bug.

Example fix

// before
if resp.StatusCode != http.StatusOK {
	return nil, fmt.Errorf("unexpected status %d from DEV.to API", resp.StatusCode)
}
// after
if resp.StatusCode != http.StatusOK {
	body, _ := io.ReadAll(io.LimitReader(resp.Body, 512))
	return nil, fmt.Errorf("unexpected status %d from DEV.to API: %s", resp.StatusCode, body)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Go: validate config before calling
if apiKey == "" {
	return fmt.Errorf("DEV.to API key is not configured")
}

Type guard

func isStatusError(err error) (int, bool) {
	msg := err.Error()
	var code int
	if _, e := fmt.Sscanf(msg, "unexpected status %d from DEV.to API", &code); e == nil {
		return code, true
	}
	return 0, false
}

Try / catch

articles, err := client.FetchArticles(ctx)
if err != nil {
	var code int
	if fmt.Sscanf(err.Error(), "unexpected status %d from DEV.to API", &code) == nil && code == http.StatusTooManyRequests {
		time.Sleep(backoff) // retry later
		return
	}
	log.Printf("devto fetch failed: %v", err)
	return
}

Prevention

When it happens

Trigger: Any non-200 response from the DEV.to API during FetchArticles: missing/invalid api-key header (401/403), malformed query params like page or tag (422), rate limiting (429), or DEV.to server errors (500/502/503).

Common situations: Expired or unset DEV_TO_API_KEY, hitting DEV.to's rate limit during frequent refreshes, transient DEV.to outages, or proxy/gateway returning an HTML error page with a non-200 status.

Related errors


AI-assisted analysis of wtfutil/wtf@bb838c1ccb (2026-09-03). Data as JSON: /api/errors/154b2bea835a2ed6. Report an issue: GitHub.