# How Holehe Parses API Responses and Handles Error Messages

> Learn how Holehe parses API responses by normalizing service interactions into a standard dictionary contract and handling errors gracefully using launch_module and print_result.

- Repository: [Palenath/holehe](https://github.com/megadose/holehe)
- Tags: internals
- Published: 2026-08-25

---

**Holehe normalizes every service interaction into a standardized dictionary contract, using `launch_module` in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) to catch exceptions and `print_result` to surface error details from the `others` field.**

Holehe, the open-source OSINT tool created by megadose, verifies email address existence across hundreds of platforms by orchestrating concurrent HTTP requests. Understanding how Holehe parses API responses and error messages reveals a robust architecture that converts diverse JSON and HTML payloads into uniform result structures while gracefully handling rate limits and network failures.

## The Async Module Contract

Every Holehe service module implements an identical asynchronous interface. Each module is an **async function** accepting three parameters: the target `email` string, an `httpx.AsyncClient` instance, and a shared `out` list.

The function performs its HTTP request—either via `await client.get()`, `post()`, or HTML fetching—and **must append a standardized dictionary** to the `out` list before returning. This contract ensures the core execution engine can aggregate results from hundreds of concurrent modules without knowing implementation details of individual services.

```python

# Simplified structure of a Holehe module

async def github(email: str, client: httpx.AsyncClient, out: list):
    resp = await client.get(f"https://api.github.com/users/{email}")
    # Parsing logic here...

    out.append({
        "domain": "github.com",
        "rateLimit": False,
        "error": False,
        "exists": True,
        "emailrecovery": None,
        "phoneNumber": None,
        "others": None,
    })

```

## Parsing JSON and HTML Responses

Holehe modules handle two primary response formats, selecting the appropriate parser based on the service's API design.

### JSON Response Handling

For REST APIs, modules call `response.json()` to deserialize payloads. The code then inspects specific fields that indicate account existence or error states—such as `"message": "Not Found"` or user profile data.

```python
data = resp.json()
if data.get("message") == "Not Found":
    exists = False
else:
    exists = True

```

### HTML Response Handling

For services returning HTML, Holehe uses **BeautifulSoup** to parse the DOM. Modules import `from bs4 import BeautifulSoup` and search for error banners, account status indicators, or specific text patterns that reveal whether an email is registered.

```python
from bs4 import BeautifulSoup

soup = BeautifulSoup(response.text, 'html.parser')
error_banner = soup.find('div', {'class': 'error-message'})
if error_banner and 'not found' in error_banner.text.lower():
    exists = False

```

## HTTP Status Code Inspection and Rate Limiting

Before parsing payloads, modules check `resp.status_code`. A **429 status code** (or rate-limit headers) triggers an immediate short-circuit that sets the `rateLimit` flag to `True` and skips further processing.

```python
if resp.status_code == 429:
    out.append({
        "domain": "github.com",
        "rateLimit": True,
        "error": False,
        "exists": False,
        "emailrecovery": None,
        "phoneNumber": None,
        "others": None,
    })
    return

```

## Exception Handling in the Core Launcher

When a module raises an unexpected exception—whether from network timeouts, parsing errors, or service changes—the **core launcher** catches it and constructs a standardized error record. According to the source code in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py), the `launch_module` function (lines 166-178) wraps module execution in a try-except block that extracts the module name and populates the error fields.

```python

# Simplified logic from holehe/core.py launch_module

try:
    await module(email, client, out)
except Exception:
    name = str(module).split('<function ')[1].split(' ')[0]
    out.append({
        "name": name,
        "domain": data[name],  # Module-to-domain mapping

        "rateLimit": False,
        "error": True,
        "exists": False,
        "emailrecovery": None,
        "phoneNumber": None,
        "others": None,
    })

```

## Normalized Output Schema and Error Messages

Every module returns a dictionary containing seven standardized keys that the printer expects:

- **`domain`**: Human-readable site name (e.g., *github.com*).
- **`rateLimit`**: Boolean indicating if the service returned a 429 or rate-limit header.
- **`error`**: Boolean set to `True` when an unexpected error occurs.
- **`exists`**: Boolean indicating if the email is known to exist on the service.
- **`emailrecovery`** / **`phoneNumber`**: Optional recovered contact data.
- **`others`**: A catch-all dictionary that may contain `"errorMessage"` or auxiliary metadata.

## Displaying Errors to the User

The **printer** logic in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) (lines 124-132) processes the aggregated results. When `error` is `True`, it checks `results["others"]` for an `errorMessage` key, prefixes the domain with `[!]`, and displays the message in red.

```python
if results.get("error"):
    toprint = ""
    if results["others"] and "errorMessage" in results["others"]:
        toprint = " Error message: " + results["others"]["errorMessage"]
    print(colored("[!] " + results["domain"] + toprint, "red"))

```

## Summary

- **Uniform Interface**: Every module is an async function writing to a shared `out` list with a standardized schema.
- **Dual Parsing**: Holehe parses JSON via `response.json()` and HTML via **BeautifulSoup** DOM selectors.
- **Rate Limit Detection**: HTTP 429 responses set `rateLimit: True` and bypass payload parsing.
- **Centralized Error Handling**: The `launch_module` function in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) (lines 166-178) catches all exceptions and forces `error: True` in the output record.
- **User-Friendly Reporting**: The printer extracts `others["errorMessage"]` to display service-specific errors prefixed with `[!]` (lines 124-132).

## Frequently Asked Questions

### How does Holehe handle rate-limited responses from services?

Holehe interprets HTTP **429 status codes** as rate-limit conditions. When a module detects this status, it immediately appends a result dictionary with `"rateLimit": True` to the output list and returns without parsing the response body, preventing unnecessary processing and clearly flagging the restriction to the user.

### What parsing libraries does Holehe use for HTML responses?

Holehe uses **BeautifulSoup** (`from bs4 import BeautifulSoup`) to parse HTML responses. Modules search for specific DOM selectors, error banners, or text patterns that indicate account status, allowing Holehe to extract intelligence from services that do not provide JSON APIs.

### Where does Holehe catch exceptions when a module crashes?

The **core launcher** in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) wraps each module call in a try-except block within the `launch_module` function (lines 166-178). When an exception occurs, it extracts the module name and appends an error record with `"error": True`, ensuring that one failing module does not crash the entire scan.

### What standardized keys must every Holehe module include in its output?

Every module must return a dictionary containing: `domain` (site name), `rateLimit` (boolean), `error` (boolean), `exists` (boolean), `emailrecovery` (string or None), `phoneNumber` (string or None), and `others` (dictionary or None). The `others` field specifically stores `"errorMessage"` for service-specific error details.