{"record":{"id":"bbfab4ae17c0fa12","repo":"lissy93/web-check","slug":"hackertarget-unavailable","errorCode":null,"errorMessage":"hackerTarget unavailable","messagePattern":"hackerTarget unavailable","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"api/subdomains.js","lineNumber":40,"sourceCode":"};\n\nconst crtSh = async (domain) => {\n  const res = await httpGet('https://crt.sh/', {\n    params: { q: `%.${domain}`, output: 'json' },\n    headers: { Accept: 'application/json' },\n    timeout: SOURCE_TIMEOUT,\n  });\n  if (!Array.isArray(res.data)) throw new Error('crt.sh returned an unexpected response');\n  return res.data.flatMap((row) => String(row?.name_value ?? '').split('\\n'));\n};\n\nconst hackerTarget = async (domain) => {\n  const res = await httpGet('https://api.hackertarget.com/hostsearch/', {\n    params: { q: domain },\n    timeout: SOURCE_TIMEOUT,\n  });\n  const body = typeof res.data === 'string' ? res.data : '';\n  if (!body || /error|api count|quota/i.test(body)) throw new Error('hackerTarget unavailable');\n  return body.split('\\n').map((line) => line.split(',')[0]);\n};\n\nconst SOURCES = [\n  { name: 'certSpotter', lookup: certSpotter },\n  { name: 'crt.sh', lookup: crtSh },\n  { name: 'hackerTarget', lookup: hackerTarget },\n];\n\nconst isTransient = (error) => {\n  const status = error.response?.status;\n  if (status && status < 500) return false;\n  return true;\n};\n\nconst subdomainsHandler = async (url) => {\n  const { hostname } = parseTarget(url);\n  if (isIpAddress(hostname)) {","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/lissy93/web-check/blob/af1a97759fc8bcc43c876c94f2ccb018ce215f90/api/subdomains.js#L22-L58","documentation":"hackerTarget's hostsearch endpoint returns plain text; on failure it returns text containing 'error', 'api count', or 'quota'. If the body is empty or matches that pattern, this error is thrown. It is effectively a quota/availability detector for hackertarget.com's free tier.","triggerScenarios":"Exceeding hackertarget's free daily API count for your IP, receiving their literal error text, or an empty response body.","commonSituations":"Repeated subdomain enumeration from one IP or CI runner exhausting the free quota; shared NAT egress (office/VPN) pooling many users into one quota.","solutions":["Expect and tolerate failure: this source should degrade gracefully while certSpotter/crt.sh still answer","Reduce request frequency or cache subdomain results per domain","Purchase hackertarget API access or run from a different egress if the quota is the bottleneck"],"exampleFix":"// before\nconst hosts = await hackerTarget(domain); // throws 'hackerTarget unavailable' after quota\n\n// after\nlet hosts = [];\ntry { hosts = await hackerTarget(domain); }\ncatch (e) { if (/quota|api count/i.test(e.message)) console.warn('hackerTarget quota hit; skipping'); else throw e; }","handlingStrategy":"fallback","validationCode":"null","typeGuard":"const isUsableBody = (s) => typeof s === 'string' && s.length > 0 && !/error|api count|quota/i.test(s);","tryCatchPattern":"try { return await hackerTarget(domain); }\ncatch (e) {\n  if (e.message === 'hackerTarget unavailable') { console.warn('skipping hackertarget'); return []; }\n  throw e;\n}","preventionTips":["Assume the free quota will run out; design for source absence","Cache hostsearch results per domain","Consider paid hackertarget access or alternate sources for high-volume pipelines"],"tags":["third-party-api","hackertarget","quota","subdomains"],"backgroundTag":"api-rate-limit-exceeded","analyzedSha":"af1a97759fc8bcc43c876c94f2ccb018ce215f90","analyzedAt":"2026-08-27T11:44:27.410Z","schemaVersion":2},"datasetVersion":"2026-08-27T13:17:12.746Z"}