Standardized Output Format for Holehe Modules: A Complete Guide to OSINT Result Structuring

Each holehe module returns a dictionary with nine standardized keys appended to a shared out list, enabling uniform OSINT aggregation across all supported services.

The holehe framework by megadose unifies email reconnaissance across hundreds of platforms through a strict output contract. Every asynchronous module must populate the same dictionary schema, allowing the core engine to process results without service-specific logic. This article examines the exact field specifications, implementation in the source code, and how to build compliant custom modules.

The Nine-Field Output Schema

Every module in holehe appends a dictionary to the shared out list with these mandatory keys:

Field Type Purpose
name str Service identifier (e.g., "twitter", "facebook")
domain str Primary domain of the service
method str Interaction type—typically "register" for account existence checks
frequent_rate_limit bool Indicates if the service enforces strict rate limiting
rateLimit bool True when the current request was blocked or throttled
exists bool True if the email is registered on the platform
emailrecovery str | None Partial email shown in password recovery flows
phoneNumber str | None Phone number mask if exposed during checks
others dict | None Additional metadata (full name, registration date, etc.)

This schema is enforced through convention rather than validation—modules that deviate will cause downstream errors in print_result or export functions.

How the Core Engine Enforces the Format

The standardization mechanism resides in [holehe/core.py](https://github.com/megadose/holehe/blob/master/holehe/core.py), specifically the launch_module function around lines 66-71. This coroutine receives each loaded module, the target email, an HTTP client, and the shared out list:


# From holehe/core.py (conceptual flow)

async def launch_module(module, email, client, out):
    await module(email, client, out)
    # Module responsible for appending dict to out

The out list accumulates results from all concurrent module executions. After completion, print_result iterates over this list expecting the nine-field structure, while export_csv maps these dictionaries directly to column headers.

Module discovery occurs through import_submodules (lines 36-47), which recursively imports all Python files under holehe/modules/. Any file defining an async function matching the signature async def servicename(email, client, out) becomes automatically available.

Real-World Implementation Examples

Twitter Module: Minimal Schema Compliance

The Twitter checker in [holehe/modules/social_media/twitter.py](https://github.com/megadose/holehe/blob/master/holehe/modules/social_media/twitter.py) (lines 17-31) demonstrates the baseline implementation:


# Simplified from twitter.py

out.append({
    "name": "twitter",
    "domain": "twitter.com",
    "method": "register",
    "frequent_rate_limit": False,
    "rateLimit": False,
    "exists": True,  # Determined from API response

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

This module performs a single POST request to Twitter's validation endpoint and translates the HTTP response directly into the standardized format.

Facebook Module: Complex Logic, Same Structure

The Facebook checker in [holehe/modules/social_media/facebook.py](https://github.com/megadose/holehe/blob/master/holehe/modules/social_media/facebook.py) (lines 30-82) shows how intricate authentication flows—CSRF token extraction, cookie management, redirect following—still converge on identical output:


# Conceptual excerpt from facebook.py

out.append({
    "name": "facebook",
    "domain": "facebook.com",
    "method": "register",
    "frequent_rate_limit": True,
    "rateLimit": False,
    "exists": exists,  # Boolean from response parsing

    "emailrecovery": None,
    "phoneNumber": None,
    "others": others,  # May contain 'name' or 'profile_photo'

})

Despite requiring multiple requests and header manipulation, the final dictionary adheres precisely to the schema.

Creating Compliant Custom Modules

To extend holehe with new services, implement the standard output format as follows:


# custom_module.py — place in holehe/modules/category/

import httpx

async def newservice(email, client, out):
    """
    Example custom module following holehe's standardized output format.
    """
    name = "newservice"
    domain = "newservice.com"
    method = "register"
    frequent_rate_limit = False
    
    try:
        response = await client.post(
            "https://newservice.com/api/v1/users/check",
            json={"email": email},
            timeout=10
        )
        
        data = response.json()
        exists = data.get("account_exists", False)
        others = None
        
        if exists and "username" in data:
            others = {"username": data["username"]}
        
        result = {
            "name": name,
            "domain": domain,
            "method": method,
            "frequent_rate_limit": frequent_rate_limit,
            "rateLimit": False,
            "exists": exists,
            "emailrecovery": data.get("recovery_hint"),
            "phoneNumber": None,
            "others": others,
        }
        
    except httpx.HTTPStatusError as e:
        # Rate limited or blocked

        result = {
            "name": name,
            "domain": domain,
            "method": method,
            "frequent_rate_limit": frequent_rate_limit,
            "rateLimit": e.response.status_code == 429,
            "exists": False,
            "emailrecovery": None,
            "phoneNumber": None,
            "others": None,
        }
        
    except Exception:
        # Generic failure

        result = {
            "name": name,
            "domain": domain,
            "method": method,
            "frequent_rate_limit": frequent_rate_limit,
            "rateLimit": False,
            "exists": False,
            "emailrecovery": None,
            "phoneNumber": None,
            "others": None,
        }
    
    out.append(result)

Place this file anywhere under holehe/modules/ and restart holehe—the import_submodules function will detect and load it automatically.

Consuming Standardized Output Programmatically

The uniform structure enables reliable downstream processing:

import asyncio
import json
from holehe.core import launch_module
from holehe.modules.social_media import twitter

async def analyze(email):
    async with httpx.AsyncClient() as client:
        results = []
        await launch_module(twitter.twitter, email, client, results)
        
        # Guaranteed fields regardless of service

        for r in results:
            print(f"{r['name']}: {'Found' if r['exists'] else 'Not found'}")
            if r['rateLimit']:
                print("  ⚠️ Rate limited")
            if r['others']:
                print(f"  Extra: {r['others']}")

# Execute

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

This predictability is why the export functions in [core.py](https://github.com/megadose/holehe/blob/master/holehe/core.py) can generate CSV, JSON, or terminal tables without service-specific handlers.

Summary

Frequently Asked Questions

What happens if a module omits required fields in the output dictionary?

The core engine will raise a KeyError during result formatting in print_result or export operations. While the launch_module function itself does not validate the dictionary, downstream consumers expect all nine fields. Missing keys cause crashes when generating CSV headers or terminal tables.

Can the others field contain nested structures beyond flat dictionaries?

Yes. The others field accepts any JSON-serializable value, though flat dictionaries are conventional. Some modules store strings like full names, while others use nested objects with multiple metadata points. The export functions handle this through standard JSON serialization.

How does holehe handle rate limiting uniformly across different services?

Each module sets rateLimit to True when detecting HTTP 429 responses, CAPTCHA challenges, or authentication blocks. The frequent_rate_limit flag in the schema serves as documentation—warning users that a service aggressively limits requests. The core engine does not automatically retry; modules must implement their own backoff logic or fail fast with rateLimit=True.

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 →