Metadata-Version: 2.4
Name: herohunt
Version: 0.1.0
Summary: Official Python SDK for the HeroHunt People Search API — search and enrich talent profiles across LinkedIn, GitHub, and StackOverflow.
Project-URL: Homepage, https://www.herohunt.ai/people-search-api
Project-URL: Documentation, https://www.herohunt.ai/people-search-api/docs
Project-URL: API Reference, https://api.herohunt.ai/people-api-docs
Project-URL: Repository, https://github.com/herohunt-ai/herohunt-python
Project-URL: Issues, https://github.com/herohunt-ai/herohunt-python/issues
Project-URL: Changelog, https://github.com/herohunt-ai/herohunt-python/blob/main/CHANGELOG.md
Author-email: HeroHunt <info@herohunt.ai>
License: MIT
License-File: LICENSE
Keywords: ai-agent,enrichment,github,herohunt,linkedin,people-search,recruiter,recruitment,sdk,stackoverflow,talent-sourcing
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx<1.0,>=0.27
Requires-Dist: pydantic<3.0,>=2.6
Requires-Dist: typing-extensions>=4.7; python_version < '3.11'
Provides-Extra: dev
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Description-Content-Type: text/markdown

# herohunt

[![PyPI](https://img.shields.io/pypi/v/herohunt.svg)](https://pypi.org/project/herohunt/)
[![Python](https://img.shields.io/pypi/pyversions/herohunt.svg)](https://pypi.org/project/herohunt/)
[![License](https://img.shields.io/pypi/l/herohunt.svg)](https://github.com/herohunt-ai/herohunt-python/blob/main/LICENSE)
[![Types](https://img.shields.io/badge/types-mypy%20strict-blue)](https://github.com/herohunt-ai/herohunt-python)

Official Python SDK for the [HeroHunt People Search API](https://www.herohunt.ai/people-search-api). Search and enrich talent profiles across LinkedIn, GitHub, and StackOverflow with one natural-language call.

- Synchronous **and** asynchronous clients sharing the same surface
- Fully typed (pydantic v2 models, `py.typed` marker, `mypy --strict` clean)
- Automatic retries with exponential backoff and `Retry-After` support
- Per-response credit + rate-limit metadata
- 100% test coverage of public endpoints

## Install

```bash
pip install herohunt
```

## Get an API key

Sign up at [herohunt.ai/api/sign-up](https://www.herohunt.ai/api/sign-up). The free tier gives you credits to play with right away.

Export your key once:

```bash
export HEROHUNT_API_KEY=ps_live_your_key
```

## Quick start

```python
from herohunt import HeroHunt

client = HeroHunt()  # reads HEROHUNT_API_KEY from env

result = client.people_search.search(
    "Senior React engineers in Berlin with TypeScript",
    max_results=50,
    enrich=True,
)

print(f"Found {result.total_results} profiles")
for profile in result.results:
    print(f"- {profile.first_name} {profile.last_name} — {profile.headline}")
    print(f"  {profile.profile_url}")
    if profile.emails:
        print(f"  Email: {profile.emails[0].value}")
```

### Paginate the same search for free

```python
page_2 = client.people_search.paginate(result.search_id, page=2)
```

### Async

```python
import asyncio
from herohunt import AsyncHeroHunt

async def main() -> None:
    async with AsyncHeroHunt() as client:
        result = await client.people_search.search(
            "Staff Go engineers in San Francisco",
            max_results=25,
        )
        for profile in result.results:
            print(profile.first_name, profile.headline)

asyncio.run(main())
```

## Account & billing

```python
usage = client.account.usage()
print(f"Plan: {usage.plan}")
print(f"Credits: {usage.credits_remaining}/{usage.credits_total}")
print(f"Rate limit: {usage.rate_limit} req/min")

history = client.account.searches(limit=10)
for past in history.items:
    print(past.created_at, past.query, past.total_results)
```

## API keys

```python
key = client.api_keys.create("CI bot")
print(key.api_key)  # full key shown ONLY ONCE — store it now

for k in client.api_keys.list():
    print(k.key_prefix, k.name, k.scopes)

# Rotate or revoke
client.api_keys.rotate(key_id="...")
client.api_keys.revoke(key_id="...")
```

## Errors

Every error inherits from `HeroHuntError`, so catching that is always safe. The SDK exposes typed subclasses so you can branch on the failure mode without parsing strings.

```python
from herohunt import (
    HeroHunt,
    AuthenticationError,
    InsufficientCreditsError,
    RateLimitError,
)

client = HeroHunt()

try:
    result = client.people_search.search("Backend engineers in Lisbon")
except AuthenticationError:
    print("Bad or revoked API key — generate a new one at the dashboard.")
except InsufficientCreditsError as exc:
    print(f"Out of credits: {exc.credits_remaining} left. Resets {exc.credit_reset_date}.")
except RateLimitError as exc:
    print(f"Slow down — retry in {exc.retry_after}s.")
```

| Status | Exception                  | Why                                       |
|--------|----------------------------|-------------------------------------------|
| 400    | `BadRequestError`          | Missing `query` / `search_id`, bad enums  |
| 401    | `AuthenticationError`      | API key missing, invalid, or revoked      |
| 402    | `InsufficientCreditsError` | Not enough credits for the reservation    |
| 403    | `ForbiddenError`           | Scope missing or IP not allowlisted       |
| 404    | `NotFoundError`            | Search / key / account not found          |
| 429    | `RateLimitError`           | Per-minute rate limit exceeded            |
| 5xx    | `ServerError`              | Upstream failure (also auto-retried)      |
| —      | `APIConnectionError`       | Local network error                       |

## Inspecting credit usage

Every response object carries a `response_meta` field built from the response headers:

```python
result = client.people_search.search("Data engineers in Toronto", max_results=30)
print(result.response_meta.credits_used)       # e.g. 30
print(result.response_meta.credits_remaining)  # remaining balance
print(result.response_meta.request_id)         # to share with support
```

## Configuration

```python
from herohunt import HeroHunt

client = HeroHunt(
    api_key="ps_live_...",                   # or HEROHUNT_API_KEY env var
    base_url="https://api.herohunt.ai",       # or HEROHUNT_BASE_URL env var
    timeout=120.0,                            # seconds, applied per call
    max_retries=2,                            # 429 / 5xx auto-retries
    default_headers={"X-My-Trace": "..."},   # added to every request
)
```

Pass a custom `httpx.Client` / `httpx.AsyncClient` via `http_client=` to plug in your own transport, proxy, or instrumentation.

## Resources

- API reference: [herohunt.ai/people-search-api/docs](https://www.herohunt.ai/people-search-api/docs)
- OpenAPI spec: [api.herohunt.ai/people-api-docs](https://api.herohunt.ai/people-api-docs)
- Product page: [herohunt.ai/people-search-api](https://www.herohunt.ai/people-search-api)
- MCP server companion: [herohunt-mcp on npm](https://www.npmjs.com/package/herohunt-mcp)
- Issue tracker: [github.com/herohunt-ai/herohunt-python/issues](https://github.com/herohunt-ai/herohunt-python/issues)

## License

MIT — see [LICENSE](./LICENSE).
