Python client for the TechRecon JA4 + web-crawl technology intelligence API
Project description
techrecon (Python client)
Python client for the TechRecon JA4 fingerprinting +
web-crawl technology intelligence API. Generated from docs/openapi.yaml
(spec version 1.1.0).
- Zero runtime dependencies — built on
urllib.requestfrom the standard library only. - Typed — one method per endpoint,
TypedDictresponse 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)
pip install techrecon
Until then, install from a local checkout:
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
from techrecon import TechRecon
client = TechRecon(api_key="<YOUR_TECHRECON_API_KEY>")
Point at a local dev server instead of production with base_url:
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:
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)")
4 technologies detected
WordPress (0.99 confidence)
PHP (0.95 confidence)
Or look up a JA4 TLS fingerprint directly:
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:
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 below for the full hierarchy.
5. That's it
You now have a working client. See 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):
result = client.bulk_domain_lookup(["example.com", "wordpress.org"])
for r in result["results"]:
print(r["domain"], r["tech_count"])
Search by technology:
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):
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:
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:
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
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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file techrecon-0.1.0.tar.gz.
File metadata
- Download URL: techrecon-0.1.0.tar.gz
- Upload date:
- Size: 18.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f0d55613289f090b459ee81c43db58268f2158a7deba39d8709b38c10e4b5c52
|
|
| MD5 |
f3ddc2312a838978b5597d6892134b96
|
|
| BLAKE2b-256 |
762349e4e806ecb163da44156e98786c74f95c9d44cc3e46360f2ea5067126a3
|
File details
Details for the file techrecon-0.1.0-py3-none-any.whl.
File metadata
- Download URL: techrecon-0.1.0-py3-none-any.whl
- Upload date:
- Size: 15.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
08d4bfbc6fe62fb1e2d3e649180c0e9b0de5b75b12e912d92403a981debb9493
|
|
| MD5 |
97dc45630df59c5c4e76992df305edde
|
|
| BLAKE2b-256 |
5391572b711a5f428a3ab093614ed66de359ae3202ebfbd1f14eae678f616bb4
|