Understanding Holehe's Rate Limit Detection and Handling Mechanisms

Holehe detects rate limits at the service-module level, not centrally, by catching HTTP 429 responses and exceptions, then flags them with "rateLimit": True in a standardized result dictionary that the core runner displays as a yellow [x] marker.

Holehe is an open-source email enumeration tool that checks hundreds of services for account existence. Understanding how it handles rate limit detection and handling mechanisms is essential for interpreting results correctly and avoiding false negatives when services throttle requests. This analysis examines the actual implementation in the megadose/holehe repository to reveal the architectural patterns behind its rate-limiting logic.

Distributed Detection: Rate Limits in Service Modules

Holehe's architecture pushes all rate-limit detection into individual service modules rather than implementing centralized middleware. Each module in holehe/modules/ follows a strict contract:

  • Accepts email, httpx.AsyncClient, and a shared out list
  • Appends a result dictionary with "rateLimit": False on success
  • Appends "rateLimit": True when throttled or when unexpected errors occur

This design allows modules to interpret service-specific error semantics while maintaining uniform output.

The Tumblr Module: Exception-Based Detection

The Tumblr module (holehe/modules/social_media/tumblr.py) exemplifies the exception-driven pattern:

async def tumblr(email, client, out):
    # ... setup code ...

    try:
        post = await client.post("https://www.tumblr.com/api/v2/user/validate", ...)
        if post.status_code != 400:
            raise Exception("xc")
        error = post.json()["response"]["code"]
        # ... success handling ...

    except Exception:
        # Any exception triggers rate-limit flag

        out.append({
            "name": "tumblr",
            "domain": "tumblr.com",
            "method": "register",
            "frequent_rate_limit": True,
            "rateLimit": True,      # <-- rate limit detected

            "exists": False,
            "emailrecovery": None,
            "phoneNumber": None,
            "others": None,
        })

The module expects HTTP 400 with specific JSON error codes (2 for existing user, 1030 for password too short). Any deviation—including HTTP 429, network failures, or malformed responses—triggers the catch-all except block, setting "rateLimit": True.

Explicit Status Code Checking: The Vivino Module

Some modules check HTTP 429 directly. In holehe/modules/shopping/vivino.py:

response = await client.post(...)
if response.status_code == 429:
    out.append({
        # ...

        "rateLimit": True,
        # ...

    })

This explicit check complements the exception-based approach, handling services that reliably return standard status codes.

Centralized Reporting: From Dictionary to Display

While detection is distributed, reporting is centralized in holehe/core.py. The print_result function aggregates module outputs and renders visual indicators:


# holehe/core.py lines 13-15

description = (
    "[+] Email used",
    "[−] Email not used",
    "[x] Rate limit",    # index 2: yellow marker

    "[!] Error"
)

# Rendering logic

if results["rateLimit"] and args.onlyused == False:
    websiteprint = print_color("[x] " + results["domain"], "yellow", args)
    print(websiteprint)

The [x] marker appears in yellow consistently across all rate-limited services, regardless of which module detected the condition.

Practical Usage and Programmatic Access

Command-Line Observation

Run Holehe and watch for rate-limit markers in output:

holehe target@example.com

Typical output pattern:


[+] target@example.com
[+] instagram.com         # account exists

[x] tumblr.com            # rate-limited (yellow marker)

[−] twitter.com           # email not used

[!] github.com Error ...  # other error

Programmatic Rate-Limit Extraction

Import Holehe's core functions to filter rate-limited services programmatically:

import asyncio
import httpx
from holehe.core import import_submodules, get_functions

async def check_with_rate_limit_details(email: str):
    modules = import_submodules("holehe.modules")
    sites = get_functions(modules)
    client = httpx.AsyncClient()
    out = []
    
    for site in sites:
        try:
            await site(email, client, out)
        except Exception:
            continue  # module-level exceptions don't propagate

    
    rate_limited = [r for r in out if r.get("rateLimit")]
    
    print(f"\nRate-limited services ({len(rate_limited)}):")
    for r in rate_limited:
        print(f"  - {r['domain']} (method: {r['method']})")
    
    return rate_limited

asyncio.run(check_with_rate_limit_details("target@example.com"))

This pattern enables retry logic, backoff strategies, or service-specific handling based on the "rateLimit" flag.

Architectural Trade-offs

Holehe's approach to rate limit detection and handling involves deliberate design choices:

  • Per-service detection allows nuanced interpretation of service-specific error semantics
  • Boolean flag standardization enables uniform downstream processing
  • Exception-driven defaults err toward marking unknown errors as rate-limited rather than false negatives
  • No automatic retry—the tool reports limits but leaves mitigation to the user

The "frequent_rate_limit" field in result dictionaries hints at services particularly prone to throttling, allowing users to prioritize or deprioritize specific checks.

Summary

  • Detection is decentralized: Each module in holehe/modules/ implements its own rate-limit logic based on HTTP status codes, JSON error fields, or exception handling
  • Output is standardized: Every result dictionary contains "rateLimit": True/False regardless of detection method
  • Display is centralized: holehe/core.py renders yellow [x] markers for all rate-limited services uniformly
  • Pattern variants exist: Tumblr uses exception-based detection; Vivino uses explicit status_code == 429 checks
  • No built-in backoff: The tool detects and reports limits but does not implement automatic retry or rate-limit avoidance

Frequently Asked Questions

How does Holehe distinguish between rate limits and other errors?

Holehe does not strictly distinguish them at the module level. The catch-all exception handlers in many modules—exemplified by holehe/modules/social_media/tumblr.py—treat any unexpected response as a potential rate limit. Services with explicit 429 checks like Vivino provide more precise detection, but both patterns ultimately set the same "rateLimit": True flag.

Can I configure Holehe to retry rate-limited requests automatically?

No automatic retry mechanism exists in the current megadose/holehe codebase. The architecture reports rate limits through the "rateLimit" boolean but expects external handling. Users must implement retry loops, exponential backoff, or proxy rotation outside Holehe's core execution flow.

Why does Holehe use per-module detection instead of centralized middleware?

Service APIs vary widely in how they signal throttling—HTTP 429, custom JSON error codes, invisible redirects, or silent failures. Per-module detection allows each implementation to interpret its target service's specific semantics while the core runner maintains a consistent contract through the standardized result dictionary format.

What does the frequent_rate_limit field indicate in result dictionaries?

This boolean metadata flag, present in all module outputs, identifies services particularly prone to aggressive throttling. It enables users to make informed decisions about which services to prioritize, skip, or handle with special care during large-scale enumeration campaigns.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →