projectdiscovery/subfinder · warning
virustotal quota exhausted (HTTP 429); some subdomains for
Error message
virustotal quota exhausted (HTTP 429); some subdomains for %s may be missing
What it means
The virustotal source replaces the generic status-code error with this message when the API returns HTTP 429. VirusTotal's free tier allows 500 requests/day; once exhausted every call returns 429 and some subdomains may be missing. The library raises this so operators know to switch to an enterprise key or reduce request scope (issue #1718).
Solutions
- Supply a VirusTotal Premium/enterprise API key with higher quotas.
- Reduce scope: enumerate fewer domains per day or use narrower queries to stay under 500 requests/day.
- Wait for the daily quota window to reset before re-running the enumeration.
Example fix
// before: free key exhausted mid-run export VIRUSTOTAL_API_KEY=free_key // after: use an enterprise key or spread queries across days export VIRUSTOTAL_API_KEY=<enterprise_key>
Defensive patterns
Strategy: validation
Validate before calling
// check remaining quota cheaply before a run
resp, err := http.Get("https://www.virustotal.com/api/v3/domains/example.com" + "?limit=1") // with key header
if resp != nil && resp.StatusCode == http.StatusTooManyRequests {
log.Fatal("VirusTotal quota exhausted; use enterprise key or wait for reset")
} Try / catch
for r := range results {
if r.Type == subscraping.Error && strings.Contains(r.Error.Error(), "quota exhausted") {
// stop VT-dependent work; switch key or schedule retry after reset
}
} Prevention
- Budget domain enumerations against the 500 requests/day free-tier cap.
- Avoid sharing one free VT key across multiple tools or CI jobs.
- Prefer an enterprise key for large or recurring scans.
- Persist enumeration results so re-runs do not re-consume quota.
When it happens
Trigger: Enumerating a domain when the VirusTotal API key's daily (or per-minute) quota is already consumed, so the request gets http.StatusTooManyRequests (429).
Common situations: Running broad enumerations with a free-tier VT key across many domains in one day; multiple tools sharing one free key; retry loops amplifying quota consumption.
Understand the failure class
- HTTP status errors: handling 4xx and 5xx responses — how to handle 4xx and 5xx responses properly.
Related errors
- unexpected status code
- invalid source specified in -rls flag
- unexpected status code
- %s
- request failed with status
AI-assisted analysis of projectdiscovery/subfinder@7a0b91f0fa (2026-09-06).
Data as JSON: /api/errors/cd910bd4284a0dfd.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/subscraping/sources/virustotal/virustotal.go:78
for {
select {
case <-ctx.Done():
return
default:
}
var url = fmt.Sprintf("https://www.virustotal.com/api/v3/domains/%s/subdomains?limit=40", domain)
if cursor != "" {
url = fmt.Sprintf("%s&cursor=%s", url, cursor)
}
s.requests++
resp, err := session.Get(ctx, url, "", map[string]string{"x-apikey": randomApiKey})
if err != nil {
// The free tier grants 500 requests/day; once it is exhausted every
// call returns HTTP 429. Surface an actionable message instead of the
// generic "unexpected status code 429" so operators know to supply an
// enterprise key or lower the scope (see #1718).
if resp != nil && resp.StatusCode == http.StatusTooManyRequests {
err = fmt.Errorf("virustotal quota exhausted (HTTP 429); some subdomains for %s may be missing", domain)
}
results <- subscraping.Result{Source: s.Name(), Type: subscraping.Error, Error: err}
s.errors++
session.DiscardHTTPResponse(resp)
return
}
var data response
err = jsoniter.NewDecoder(resp.Body).Decode(&data)
// Close the body per iteration; deferring inside the loop would keep
// every page's body (and its connection) open until the goroutine exits.
if closeErr := resp.Body.Close(); closeErr != nil {
results <- subscraping.Result{Source: s.Name(), Type: subscraping.Error, Error: closeErr}
s.errors++
}
if err != nil {
results <- subscraping.Result{Source: s.Name(), Type: subscraping.Error, Error: err}
s.errors++View on GitHub (pinned to 7a0b91f0fa)