Metadata-Version: 2.4
Name: techrecon
Version: 0.1.0
Summary: Python client for the TechRecon JA4 + web-crawl technology intelligence API
Author: TechRecon
License: Proprietary
Project-URL: Homepage, https://api.techrecon.io
Project-URL: Documentation, https://api.techrecon.io/v1/docs
Keywords: techrecon,ja4,technology-detection,fingerprinting
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
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
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Provides-Extra: test
Requires-Dist: pytest>=7; extra == "test"

# techrecon (Python client)

Python client for the [TechRecon](https://api.techrecon.io) JA4 fingerprinting +
web-crawl technology intelligence API. Generated from `docs/openapi.yaml`
(spec version 1.1.0).

- **Zero runtime dependencies** — built on `urllib.request` from the standard
  library only.
- **Typed** — one method per endpoint, `TypedDict` response shapes, typed
  exceptions per HTTP status code.
- Requires Python **3.10+**.

> **Not yet published to PyPI.** This package is prepared but not released;
> see the "Publishing" note in the PR description for what's still a human
> decision (final package name, PyPI upload).

## Install (once published)

```bash
pip install techrecon
```

Until then, install from a local checkout:

```bash
cd sdk/python
pip install -e .
```

## 5-minute quickstart

### 1. Get an API key

Every endpoint except health checks and docs requires an API key, sent as
the `X-API-Key` header. Get one from your TechRecon account
(`POST /v1/account/keys`), or ask your TechRecon contact for one.

### 2. Create a client

```python
from techrecon import TechRecon

client = TechRecon(api_key="<YOUR_TECHRECON_API_KEY>")
```

Point at a local dev server instead of production with `base_url`:

```python
client = TechRecon(api_key="<YOUR_TECHRECON_API_KEY>", base_url="http://localhost:8080")
```

### 3. Your first lookup

Look up a domain's detected technology stack:

```python
domain = client.get_domain("wordpress.org")
print(domain["tech_count"], "technologies detected")
for tech in domain["technologies"]:
    print(f"  {tech['technology']} ({tech['confidence']:.2f} confidence)")
```

```text
4 technologies detected
  WordPress (0.99 confidence)
  PHP (0.95 confidence)
```

Or look up a JA4 TLS fingerprint directly:

```python
result = client.lookup("t13d1516h2_8daaf6152771_b0da82dd1658", ip="93.184.216.34")
if result["ja4_tech_match"]:
    print(result["ja4_tech_match"]["technology"])  # "nginx"
else:
    print("no mapping for this fingerprint yet")
```

### 4. Handle errors

Every non-2xx response raises a typed exception carrying the status code
and the API's `{"error": "..."}` message:

```python
from techrecon import NotFoundError, RateLimitError, TechRecon

client = TechRecon(api_key="trk_live_...")
try:
    client.get_domain("does-not-exist.example")
except NotFoundError as exc:
    print(f"not found: {exc.message}")
except RateLimitError as exc:
    print(f"rate limited (429): {exc.message}")
```

See [Exceptions](#exceptions) below for the full hierarchy.

### 5. That's it

You now have a working client. See [Endpoints covered](#endpoints-covered)
below for the full method list, or browse the interactive spec at
`GET /v1/docs` on any TechRecon server.

## More examples

**Bulk domain lookup** (up to 100 domains per call):

```python
result = client.bulk_domain_lookup(["example.com", "wordpress.org"])
for r in result["results"]:
    print(r["domain"], r["tech_count"])
```

**Search by technology:**

```python
page = client.search(technology="WordPress", min_confidence=0.8, limit=50)
for hit in page["results"]:
    print(hit["domain"])
if page["has_more"]:
    next_page = client.search(technology="WordPress", cursor=page["next_cursor"])
```

**Change feed** (technology adds/removes/updates in the last N days):

```python
changes = client.get_changes(since="2026-06-01T00:00:00Z", technology="WordPress")
for event in changes["changes"]:
    print(event["domain"], event["change_type"], event["technology"])
```

**Reverse pivot from a JA4 hash to the domains that present it:**

```python
page = client.pivot_ja4("t13d1516h2_8daaf6152771_02713d6af862")
print(page["summary"]["total_domains"], "domains observed with this fingerprint")
```

**Create a Slack alert on any technology change for a domain:**

```python
alert = client.create_alert(
    channel="slack",
    endpoint="https://hooks.slack.com/services/XXX/YYY/ZZZ",
    domain="example.com",
)
print(alert["id"])
```

## Endpoints covered

This client covers the stable, public surface of `docs/openapi.yaml`.
Endpoints marked `x-internal: true` in the spec — self-serve
signup/email-verification (beta, not GA), Stripe billing (beta, being
replaced by the NAF gateway), and the privacy admin routes — are
intentionally **not** wrapped here; they're beta or admin-only per the
spec's own descriptions. Everything else is covered:

| Area           | Methods                                                                                |
| -------------- | -------------------------------------------------------------------------------------- |
| Health         | `health`, `readiness`                                                                  |
| JA4 Lookup     | `lookup`, `bulk_lookup`, `stats`, `top_unknown`                                        |
| Domain         | `get_domain`, `bulk_domain_lookup`, `get_domain_history`, `get_domain_changes`         |
| Change feed    | `get_changes`                                                                          |
| Search         | `search`                                                                               |
| Technologies   | `get_tech_stats`, `get_tech_trend`                                                     |
| Exports        | `create_export`, `get_export_status`, `download_export`                                |
| System         | `get_system_stats`, `get_system_health`                                                |
| Ingestion      | `ingest_crawl_results` (system identity only — 403 for tenant keys)                    |
| Crawl requests | `create_crawl_request`, `list_pending_crawl_requests`, `get_crawl_request_status`      |
| Priority crawl | `submit_priority_crawl`, `get_priority_crawl_status`                                   |
| IP lookup      | `ip_batch_lookup`                                                                      |
| Pivots         | `pivot_ja4`, `pivot_jarm`, `pivot_favicon`                                             |
| Alerts         | `create_alert`, `list_alerts`, `delete_alert`                                          |
| Auth           | `login`, `logout`                                                                      |
| Account        | `get_account`, `list_api_keys`, `issue_api_key`, `revoke_api_key`, `get_account_usage` |

## Exceptions

All exceptions inherit from `techrecon.TechReconError`. HTTP errors raise
`techrecon.APIError` subclasses, each carrying `.status_code`, `.message`
(the API's `error` field), and `.body` (raw response text):

| Exception                 | HTTP status                                                                                                              |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `BadRequestError`         | 400                                                                                                                      |
| `AuthenticationError`     | 401                                                                                                                      |
| `ForbiddenError`          | 403                                                                                                                      |
| `NotFoundError`           | 404                                                                                                                      |
| `ConflictError`           | 409                                                                                                                      |
| `PayloadTooLargeError`    | 413                                                                                                                      |
| `RateLimitError`          | 429 (rate limiting **and** quota exhaustion — the spec has no 402; 429 is used uniformly, e.g. alert quota, API-key cap) |
| `ServiceUnavailableError` | 503 (a required storage backend isn't configured)                                                                        |

## Development

```bash
cd sdk/python
python3 -m py_compile src/techrecon/*.py tests/*.py   # syntax check
python3 -m pytest tests/                               # run tests (network-free)
```

Tests use a fake in-memory transport (`tests/fake_transport.py`) — no
network access or live API key is required to run them.

## License

Proprietary — see `docs/openapi.yaml`'s `info.license` for the canonical
statement. Not licensed for redistribution outside TechRecon-authorized use.
