# Holehe CLI vs Programmatic API: Key Differences for OSINT Email Checks

> Explore Holehe CLI vs Programmatic API differences for OSINT email checks. Choose the best method for your needs: easy CLI output or flexible API integration.

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

---

**The Holehe CLI provides a ready-to-run terminal interface with formatted output and CSV export, while the programmatic API returns raw Python data structures for custom integration.**

Holehe is an open-source email OSINT tool by megadose that checks for account registrations across hundreds of websites. When deciding how to run Holehe, you'll choose between its command-line interface for quick investigations or its Python API for embedded workflows. Both approaches ultimately execute the same async core logic in `holehe.core`, but they differ significantly in input handling, output formatting, and control flow.

## Entry Points and Execution Flow

### CLI: Console Script to Trio Runner

The CLI installs as a console script named `holehe`, defined in [`setup.py`](https://github.com/megadose/holehe/blob/main/setup.py) as `holehe=holehe.core:main`. When invoked, this calls `holehe.core.main()`, which immediately executes `trio.run(maincore)`.

In [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) lines 32-34, the `main()` function serves as the synchronous entry point that bridges to the async world.

```python

# From holehe/core.py

def main():
    trio.run(maincore)

```

The `maincore()` function handles argument parsing via `argparse` starting at line 179, then orchestrates module execution through a Trio nursery.

### API: Direct Async Function Imports

The programmatic API skips the console script entirely. You import async functions directly from `holehe.core` and manage execution yourself.

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

async def custom_check(email):
    modules = import_submodules("holehe.modules")
    
    class Args: 
        csvoutput = False
        nopasswordrecovery = False
    
    websites = get_functions(modules, Args())
    
    async with httpx.AsyncClient(timeout=10) as client:
        out = []
        for website in websites:
            await launch_module(website, email, client, out)
        return out

```

This approach gives you fine-grained control over which modules run, concurrency strategies, and result handling.

## Argument Handling: Parsed Flags vs Direct Parameters

The CLI uses `argparse` with flags defined in `maincore()` at lines 179-196. Key options include:

- `--only-used` — Show only sites where the email is registered
- `--no-color` — Disable terminal colors
- `--csv` — Export results to a timestamped CSV file
- `--timeout` — Set request timeout in seconds

The API bypasses `argparse` completely. You construct parameter objects directly, as shown in the `Args` class above. This eliminates string parsing overhead and enables dynamic configuration at runtime.

## Output Formats: Terminal Tables vs Raw Data Structures

### CLI Output Characteristics

The CLI produces multiple side effects after the async run completes:

- **Formatted table**: Color-coded results printed via `print_result`
- **Progress bar**: Visual feedback through `TrioProgress` instrument
- **CSV export**: Optional file creation via `export_csv`
- **Terminal clearing**: Screen management for clean display

These features are implemented in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) lines 27-31.

### API Output Characteristics

The programmatic API returns a plain Python list of dictionaries containing raw findings. Each dictionary includes standard fields like `name`, `domain`, `method`, `rateLimit`, `exists`, `emailrecovery`, `phoneNumber`, and `others`.

```python

# Example returned structure

[
    {
        "name": "twitter",
        "domain": "twitter.com",
        "method": "register",
        "rateLimit": False,
        "exists": True,
        "emailrecovery": None,
        "phoneNumber": "+1-xxx-xxx-xxxx",
        "others": None
    }
]

```

No formatting, no files, no terminal manipulation — just structured data for your application to process.

## Concurrency and Control Flow

### CLI: Full Module Sweep with Fixed Orchestration

The CLI discovers all modules and launches them together inside a Trio nursery. From [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) lines 18-22:

```python
async with trio.open_nursery() as nursery:
    for website in websites:
        nursery.start_soon(launch_module, website, email, client, out)

```

This approach maximizes throughput but offers no granularity. Every discovered module runs, and the CLI blocks until completion.

### API: Selective Execution and Custom Strategies

With the API, you control exactly which modules execute and how:

```python

# Run only specific modules

target_modules = ["github", "twitter", "instagram"]
filtered = [w for w in websites if w.__name__.split(".")[-1] in target_modules]

# Custom concurrency: sequential with delays

import asyncio
for website in filtered:
    await launch_module(website, email, client, out)
    await asyncio.sleep(1)  # Rate limiting

```

## Error Handling Behavior

Both interfaces use the same `launch_module` function, which catches exceptions internally. In [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) lines 168-176, errors are recorded in the output list rather than raised:

```python
try:
    await module(email, client, out)
except Exception as e:
    name = str(module).split('<function ')[1].split(' ')[0]
    out.append({"name": name, "error": str(e)})

```

The **CLI** continues execution and prints a summary showing any failures. The **API** returns these error entries in the list, allowing your code to detect them via the `"error"` key and respond appropriately — retry, log, or raise as needed.

## When to Use Each Interface

| Scenario | Recommended Approach |
|----------|-------------------|
| One-off email investigation | **CLI** — fastest path to visual results |
| Automated security scanning | **API** — integrate with existing Python tooling |
| Custom output formatting | **API** — raw data for your own reports |
| CI/CD pipeline integration | **API** — programmatic error handling and exit codes |
| Large-scale batch processing | **API** — control rate limiting and resource usage |
| Quick verification with CSV export | **CLI** with `--csv` flag |

## Working Code Examples

### Complete CLI Usage

```bash

# Basic check with formatted output

holehe target@example.com

# Export to CSV, show only confirmed accounts

holehe target@example.com --only-used --csv --timeout 15

# No color for pipe processing

holehe target@example.com --no-color | grep "True"

```

### Complete API Integration

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

async def holehe_api_check(email, timeout=10, modules=None):
    """
    Programmatic Holehe check with optional module filtering.
    Returns structured results for integration.
    """
    # Load module discovery

    module_root = import_submodules("holehe.modules")
    
    # Minimal args namespace

    class Config:
        csvoutput = False
        nopasswordrecovery = False
    
    # Get target functions

    all_websites = get_functions(module_root, Config())
    
    # Apply module filter if specified

    if modules:
        targets = [w for w in all_websites 
                  if any(m in w.__name__ for m in modules)]
    else:
        targets = all_websites
    
    # Execute with controlled client

    results = []
    async with httpx.AsyncClient(timeout=timeout) as client:
        for site in targets:
            await launch_module(site, email, client, results)
    
    # Separate successes and errors

    successes = [r for r in results if "error" not in r]
    failures = [r for r in results if "error" in r]
    
    return {"found": successes, "errors": failures, "total": len(results)}

# Usage

output = asyncio.run(holehe_api_check(
    "target@example.com",
    modules=["github", "gitlab", "twitter"]
))

```

## Key Source Files

| File | Purpose |
|------|---------|
| [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) | Main orchestration: `main()`, `maincore()`, `launch_module()`, argument parsing, and result formatting |
| [`holehe/instruments.py`](https://github.com/megadose/holehe/blob/main/holehe/instruments.py) | `TrioProgress` class for CLI progress bars |
| [`holehe/modules/__init__.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/__init__.py) | Module discovery and loading utilities |
| [`setup.py`](https://github.com/megadose/holehe/blob/main/setup.py) | Console script entry point definition |

## Summary

- **The CLI** wraps Holehe's core in a user-friendly layer with `argparse`, colored terminal output, progress indicators, and CSV export — ideal for standalone investigations.
- **The API** exposes `launch_module`, `import_submodules`, and `get_functions` directly, returning raw Python lists for custom processing and integration.
- Both interfaces execute identical verification logic in `holehe.modules`; the difference is presentation and control.
- Choose the CLI for immediate visual results, the API for embedded automation and custom workflows.

## Frequently Asked Questions

### Can I use the CLI and API together in the same project?

Yes. Import `holehe.core` functions for programmatic use while still invoking the `holehe` command for manual checks. They share no global state and can coexist safely.

### Does the API support CSV export like the CLI?

Not automatically. The API returns raw data; you implement CSV export using Python's `csv` module or pandas. The CLI's `export_csv` function in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) demonstrates the expected format if you want to match it.

### Is there a performance difference between CLI and API?

No meaningful difference in core execution speed. Both use the same Trio-based async engine and `httpx.AsyncClient`. The CLI adds minimal overhead for progress rendering and terminal output.

### How do I filter specific modules when using the API?

Filter the list returned by `get_functions()` before calling `launch_module()`. Match against `function.__name__` or maintain an allowlist of target modules, as shown in the complete API example above.