# How to Use the holehe API in a Python Script

> Learn to use the holehe API in a Python script. Investigate emails programmatically using async patterns. Discover holehe's powerful capabilities without the CLI.

- Repository: [Palenath/holehe](https://github.com/megadose/holehe)
- Tags: how-to-guide
- Published: 2026-09-08

---

**The holehe library exposes its email investigation capabilities through the `holehe.core` module, allowing developers to programmatically query hundreds of online services using native Python async patterns without invoking the command-line interface.**

The [megadose/holehe](https://github.com/megadose/holehe) repository is primarily known as an OSINT tool for detecting where an email address has been registered, but its internal architecture is designed for library usage. By importing functions from `holehe.core`, you can dynamically load service modules, execute concurrent checks via Trio, and process results—all from within your Python applications.

## Understanding the holehe Architecture

The library’s design separates service discovery from execution, making it ideal for programmatic integration.

### Dynamic Module Loading

In [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py), the `import_submodules()` function recursively imports every Python file within the `holehe.modules` package. This discovers hundreds of service-specific checkers (e.g., Instagram, Amazon, GitHub) without hardcoding imports. According to the source, this function walks the package directory and returns a dictionary of loaded modules, enabling the library to support new services automatically as they are added to the codebase.

### Function Extraction

Once modules are loaded, `get_functions()` extracts the async checking functions from each module. Every service module in holehe contains a single async function that accepts an email address, an HTTP client, and a shared results list. The `get_functions()` helper filters the module namespace to return only these callable check functions, providing a clean list of targets for execution.

### Concurrent Execution with Trio

The `launch_module()` helper in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) standardizes error handling and result formatting when calling individual checkers. The library uses **Trio** (a structured concurrency library) rather than `asyncio` for its internal orchestration. The core logic opens a Trio nursery and spawns a task for each service function, allowing hundreds of HTTP requests to run concurrently while respecting backpressure and cancellation semantics.

## Implementing the holehe API Programmatically

Below are three production-ready patterns for embedding holehe functionality in your scripts.

### Minimal Synchronous Wrapper

For scripts that require a blocking interface, wrap the async core in `asyncio.run()`. This pattern uses `httpx.AsyncClient` for connection pooling and returns sorted results.

```python
import asyncio
import httpx
import trio
from holehe.core import import_submodules, get_functions, launch_module

async def _run_check(email: str):
    # Dynamically load all service modules

    modules = import_submodules("holehe.modules")
    check_functions = get_functions(modules)
    
    # Shared HTTP client for connection reuse

    client = httpx.AsyncClient(timeout=10)
    results = []
    
    async with trio.open_nursery() as nursery:
        for func in check_functions:
            nursery.start_soon(launch_module, func, email, client, results)
    
    await client.aclose()
    return sorted(results, key=lambda r: r["name"])

def check_email(email: str):
    """Blocking entry point for synchronous scripts."""
    return asyncio.run(_run_check(email))

# Usage

if __name__ == "__main__":
    data = check_email("target@example.com")
    for entry in data:
        print(f"{entry['name']}: {entry['exists']}")

```

### Pure Async Integration

For applications already using Trio or asyncio, call the check functions directly within your async event loop. This avoids the overhead of `asyncio.run()` and integrates with existing async contexts.

```python
import httpx
import trio
from holehe.core import import_submodules, get_functions, launch_module

async def check_email_async(email: str):
    modules = import_submodules("holehe.modules")
    functions = get_functions(modules)
    
    async with httpx.AsyncClient(timeout=10) as client:
        results = []
        async with trio.open_nursery() as nursery:
            for func in functions:
                nursery.start_soon(launch_module, func, email, client, results)
        
    return sorted(results, key=lambda r: r["name"])

# Trio entry point

if __name__ == "__main__":
    results = trio.run(check_email_async, "user@example.org")
    for r in results:
        print(r)

```

### Exporting Results to CSV

To replicate the CLI’s CSV export functionality, call `export_csv()` from `holehe.core`. This requires mocking a minimal configuration object that the function expects.

```python
import httpx
import trio
from holehe.core import import_submodules, get_functions, launch_module, export_csv

async def check_and_export(email: str):
    modules = import_submodules("holehe.modules")
    functions = get_functions(modules)
    
    async with httpx.AsyncClient(timeout=10) as client:
        results = []
        async with trio.open_nursery() as nursery:
            for func in functions:
                nursery.start_soon(launch_module, func, email, client, results)
    
    # Stub configuration object for export_csv

    class ExportConfig:
        csvoutput = True
    
    export_csv(results, ExportConfig(), email)
    return results

if __name__ == "__main__":
    trio.run(check_and_export, "admin@example.net")

```

The `export_csv()` function generates a timestamped file named `holehe_<timestamp>_<email>_results.csv` in the current working directory, identical to the command-line tool’s `-C` flag behavior.

## Key Source Files and Functions

| File Path | Purpose |
|-----------|---------|
| [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) | Contains `import_submodules()`, `get_functions()`, `launch_module()`, and `export_csv()`—the primary API surface for programmatic use. |
| `holehe/modules/` | Directory hierarchy where each `.py` file implements an async function checking a specific service (e.g., [`social_media/instagram.py`](https://github.com/megadose/holehe/blob/main/social_media/instagram.py)). |
| [`holehe/__init__.py`](https://github.com/megadose/holehe/blob/main/holehe/__init__.py) | Declares package metadata including `__version__`. |

When importing from `holehe.core`, you are interacting with the same functions that power the CLI’s main execution loop, ensuring behavioral parity between script-based and command-line usage.

## Summary

- **Dynamic Discovery**: Use `import_submodules("holehe.modules")` to automatically load all available service checkers without manual imports.
- **Async Core**: The library relies on Trio for structured concurrency; wrap calls in `trio.run()` or use `asyncio.run()` for synchronous compatibility.
- **Standardized Interface**: `launch_module()` ensures consistent error handling and result formatting across all services.
- **Data Export**: The `export_csv()` function provides built-in persistence matching the CLI’s output format.
- **Client Reuse**: Pass a shared `httpx.AsyncClient` instance to `launch_module()` for efficient connection pooling across hundreds of requests.

## Frequently Asked Questions

### Can I use holehe as a Python library instead of installing the CLI?

Yes. While holehe is packaged with a command-line entry point, its core functionality resides in `holehe.core`. By importing `import_submodules`, `get_functions`, and `launch_module`, you can execute email checks entirely within Python without spawning subprocesses or parsing shell output.

### Why does holehe use Trio instead of asyncio?

The [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) implementation uses Trio nurseries for structured concurrency, providing stricter control over task cancellation and error propagation compared to `asyncio.gather()`. When using the API, you should drive the event loop with `trio.run()` or use Trio’s guest mode if integrating with an existing `asyncio` application.

### How do I filter which services to check?

The `get_functions()` function returns a list of all available checker functions. You can filter this list by inspecting `func.__module__` or function names before passing them to the nursery. For example, exclude modules containing "shopping" by checking if `"shopping"` is in `func.__module__`.

### What format does the holehe API return?

Each service checker appends a dictionary to the results list with standardized keys including `"name"` (service identifier), `"exists"` (boolean indicating registration status), and `"email"` (the queried address). Results are typically sorted by the `"name"` key before return, as shown in the code examples above.