{"record":{"id":"cd910bd4284a0dfd","repo":"projectdiscovery/subfinder","slug":"virustotal-quota-exhausted-http-429-some-subdom","errorCode":null,"errorMessage":"virustotal quota exhausted (HTTP 429); some subdomains for %s may be missing","messagePattern":"virustotal quota exhausted \\(HTTP 429\\); some subdomains for (.+?) may be missing","errorType":"http","errorClass":null,"httpStatus":429,"severity":"warning","filePath":"pkg/subscraping/sources/virustotal/virustotal.go","lineNumber":78,"sourceCode":"\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t}\n\t\t\tvar url = fmt.Sprintf(\"https://www.virustotal.com/api/v3/domains/%s/subdomains?limit=40\", domain)\n\t\t\tif cursor != \"\" {\n\t\t\t\turl = fmt.Sprintf(\"%s&cursor=%s\", url, cursor)\n\t\t\t}\n\t\t\ts.requests++\n\t\t\tresp, err := session.Get(ctx, url, \"\", map[string]string{\"x-apikey\": randomApiKey})\n\t\t\tif err != nil {\n\t\t\t\t// The free tier grants 500 requests/day; once it is exhausted every\n\t\t\t\t// call returns HTTP 429. Surface an actionable message instead of the\n\t\t\t\t// generic \"unexpected status code 429\" so operators know to supply an\n\t\t\t\t// enterprise key or lower the scope (see #1718).\n\t\t\t\tif resp != nil && resp.StatusCode == http.StatusTooManyRequests {\n\t\t\t\t\terr = fmt.Errorf(\"virustotal quota exhausted (HTTP 429); some subdomains for %s may be missing\", domain)\n\t\t\t\t}\n\t\t\t\tresults <- subscraping.Result{Source: s.Name(), Type: subscraping.Error, Error: err}\n\t\t\t\ts.errors++\n\t\t\t\tsession.DiscardHTTPResponse(resp)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tvar data response\n\t\t\terr = jsoniter.NewDecoder(resp.Body).Decode(&data)\n\t\t\t// Close the body per iteration; deferring inside the loop would keep\n\t\t\t// every page's body (and its connection) open until the goroutine exits.\n\t\t\tif closeErr := resp.Body.Close(); closeErr != nil {\n\t\t\t\tresults <- subscraping.Result{Source: s.Name(), Type: subscraping.Error, Error: closeErr}\n\t\t\t\ts.errors++\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tresults <- subscraping.Result{Source: s.Name(), Type: subscraping.Error, Error: err}\n\t\t\t\ts.errors++","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/projectdiscovery/subfinder/blob/7a0b91f0fac01b62c65328bd771a3560ae611d6a/pkg/subscraping/sources/virustotal/virustotal.go#L60-L96","documentation":"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).","triggerScenarios":"Enumerating a domain when the VirusTotal API key's daily (or per-minute) quota is already consumed, so the request gets http.StatusTooManyRequests (429).","commonSituations":"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.","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."],"exampleFix":"// before: free key exhausted mid-run\nexport VIRUSTOTAL_API_KEY=free_key\n// after: use an enterprise key or spread queries across days\nexport VIRUSTOTAL_API_KEY=<enterprise_key>\n","handlingStrategy":"validation","validationCode":"// check remaining quota cheaply before a run\nresp, err := http.Get(\"https://www.virustotal.com/api/v3/domains/example.com\" + \"?limit=1\") // with key header\nif resp != nil && resp.StatusCode == http.StatusTooManyRequests {\n  log.Fatal(\"VirusTotal quota exhausted; use enterprise key or wait for reset\")\n}","typeGuard":null,"tryCatchPattern":"for r := range results {\n  if r.Type == subscraping.Error && strings.Contains(r.Error.Error(), \"quota exhausted\") {\n    // stop VT-dependent work; switch key or schedule retry after reset\n  }\n}","preventionTips":["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."],"tags":["network","rate-limit","virustotal","api-quota"],"backgroundTag":"rate-limit-exceeded","analyzedSha":"7a0b91f0fac01b62c65328bd771a3560ae611d6a","analyzedAt":"2026-09-06T23:52:02.109Z","contentChangedAt":"2026-09-06T23:52:02.109Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}