projectdiscovery/subfinder · error
unexpected status code
Error message
unexpected status code %d received from %s
What it means
The shodanct (Shodan Certificate Transparency) source raises this when the HTTP response status is anything other than 200 OK from the searchURL. The library includes the actual status code and URL so the caller can see exactly which endpoint rejected the request.
Solutions
- Log/inspect resp.StatusCode to identify the class of failure (auth vs rate limit vs server error).
- Ensure a valid Shodan API key is configured for the shodanct source.
- Add backoff/retry for 429/5xx responses and reduce request frequency.
Defensive patterns
Strategy: retry
Validate before calling
if os.Getenv("SHODAN_API_KEY") == "" { log.Warn("shodanct will fail with non-200 status") } Try / catch
for r := range results {
if r.Type == subscraping.Error {
var code int
if n, _ := fmt.Sscanf(r.Error.Error(), "unexpected status code %d", &code); n == 1 && (code == 429 || code >= 500) {
// retry with exponential backoff
}
}
} Prevention
- Check key validity and plan tier before enabling the shodanct source.
- Throttle concurrent requests to Shodan endpoints.
- Handle 429/5xx with backoff rather than immediate failure.
When it happens
Trigger: GET to the Shodan CT search URL returns a non-200 status: 401/403 for bad or missing API key, 429 for rate limiting, 5xx for server-side errors.
Common situations: Missing SHODANCT_API_KEY or wrong plan tier; invoking the source frequently and hitting rate limits; transient Shodan outages.
Related errors
AI-assisted analysis of projectdiscovery/subfinder@7a0b91f0fa (2026-09-06).
Data as JSON: /api/errors/cfcb4ba28d260524.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/subscraping/sources/shodanct/shodanct.go:49
defer func(startTime time.Time) {
s.timeTaken = time.Since(startTime)
close(results)
}(time.Now())
searchURL := fmt.Sprintf("https://ctl.shodan.io/api/v1/domain/%s/hostnames", domain)
s.requests++
resp, err := session.SimpleGet(ctx, searchURL)
if err != nil {
results <- subscraping.Result{Source: s.Name(), Type: subscraping.Error, Error: err}
s.errors++
session.DiscardHTTPResponse(resp)
return
}
if resp.StatusCode != http.StatusOK {
results <- subscraping.Result{
Source: s.Name(), Type: subscraping.Error,
Error: fmt.Errorf("unexpected status code %d received from %s", resp.StatusCode, searchURL),
}
s.errors++
session.DiscardHTTPResponse(resp)
return
}
defer session.DiscardHTTPResponse(resp)
var hostnames []string
if err := jsoniter.NewDecoder(resp.Body).Decode(&hostnames); err != nil {
results <- subscraping.Result{Source: s.Name(), Type: subscraping.Error, Error: err}
s.errors++
return
}
for _, hostname := range hostnames {
for _, subdomain := range session.Extractor.Extract(hostname) {
select {View on GitHub (pinned to 7a0b91f0fa)