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
- Nine mandatory fields define the holehe standardized output format:
name,domain,method,frequent_rate_limit,rateLimit,exists,emailrecovery,phoneNumber, andothers - Core enforcement occurs through the
launch_modulefunction and sharedoutlist in [holehe/core.py](https://github.com/megadose/holehe/blob/master/holehe/core.py) - Reference implementations exist in [
twitter.py](https://github.com/megadose/holehe/blob/master/holehe/modules/social_media/twitter.py) (minimal) and [facebook.py](https://github.com/megadose/holehe/blob/master/holehe/modules/social_media/facebook.py) (complex) - Custom modules must append dictionaries matching this exact schema to integrate automatically
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →