whisper-id
Three things for any Python agent, in one dependency-free package: query the Whisper security graph (7.48B nodes and 39.5B edges joining the physical, network, naming, ownership and threat layers; live totals at nic.whisper.online/stats, Cypher), give the agent a routable IPv6 identity with safe egress, and drive the full control plane.
pip install whisper-id # add [socks] for requests+SOCKS: pip install "whisper-id[socks]"
The security graph (keyless, zero setup)
The Whisper graph knows who operates a host, its threat posture, its look-alikes, the real origins behind a CDN, WHOIS history, and 15 named investigations. The direct read verbs run with no key at all (rate-limited taste, ~100/window). One import, real answers:
from whisper_id import graph
g = graph() # no key needed for the read verbs
g.assess("8.8.8.8") # -> [{'host': '8.8.8.8', 'label': 'benign-allowlisted', 'band': 'INFO', ...}]
g.identify("api.openai.com") # who operates this host -> vendor + operator roles
g.origins("cloudflare.com") # the real origin IPs behind a CDN
g.explain("paypal.com") # threat-feed score + why
Set WHISPER_API_KEY (or graph("whisper_live_...")) to lift the keyless rate limit. Every verb is sent either way; the graph decides what to answer:
g = graph("whisper_live_...") # with a key: the keyless rate limit is lifted
# raw Cypher, your own query, parameters bound as $-params (never string-built):
g.query("MATCH (h:HOSTNAME {name:$n})-[:RESOLVES_TO]->(ip) RETURN ip.name AS ip LIMIT 5", {"n": "github.com"})
# a named catalog recipe (a multi-step investigation, streamed over SSE):
g.typosquat("paypal.com") # look-alike sweep -> registered variants + verdict
g.run_flow("attack-surface", {"domain": "github.com"}) # any flow by its catalog slug
# discover the whole catalog (29 queries + flows) with no key, no network:
for r in g.recipes():
print(r["method"], "keyless" if r["keyless"] else "keyed", r["docs_url"])
Every verb maps to a catalog entry with its own docs page under whisper.security/docs (e.g. assess, identify); recipes() carries the exact docs_url for each. The 13 direct reads answer keyless; the 15 multi-step flows and the submit write channel are proxied up the same way and the graph decides. Full query reference: whisper-catalog.
Identity + egress
from whisper_id import register, egress
import requests
agent = register("my-bot") # a routable Whisper /128 identity
with egress(): # route this block through your /128
requests.get("https://api64.ipify.org").text # leaves from your Whisper IPv6
Inside the with block the standard proxy env vars point at your local Whisper proxy, so requests, httpx, urllib, and most libraries just work; on exit they're restored. Pass the proxy explicitly if you prefer:
with egress(set_env=False) as e:
requests.get(url, proxies=e.proxies)
API
| Call | Does |
|---|---|
graph(key=None) |
The security-graph namespace. Every verb is proxied to the graph with a key or without; the graph decides what to answer. Read verbs (assess, identify, explain, variants, walk, origins, history, ...) answer keyless (rate-limited), and a key lifts the limit. recipes() lists the whole catalog. |
register(name, *, new_key=False) |
Create a named agent: a routable /128. new_key=True mints a new agent and its own API key. -> Agent(address, id, name) |
egress(agent=None, *, tier="socks5", set_env=True) |
Context manager: bring up egress bound to your /128. Yields Egress (.port, .proxy_url, .socks_url, .proxies). tier="wireguard" for a routed /128. |
verify(address) |
Keyless: is address a real Whisper agent? (DANE + DNSSEC + reverse-DNS + JWS) -> bool |
verify_details(address) |
Keyless: the full verdict (is_whisper_agent, fqdn, operator, tenant, dane_ok, jws_ok, ...) or None |
rdap(address) |
Keyless: the public RDAP record for a /128, or None |
egress_ip() / ip() |
Keyless / CLI: the IP this process leaves from -> str |
Control plane: pure-HTTP (no CLI), one HTTPS call each; needs your key (arg key=... or WHISPER_API_KEY):
| Call | Does |
|---|---|
list_agents(kind="agents") |
Your fleet (kind = agents | identities | records). -> list[dict] |
policy(default=..., allow=..., block=...) |
Set your DNS resolver policy, or read it with no args. -> dict |
logs(agent=..., kind=..., from_=..., to=..., limit=...) |
Recent DNS/conn/alloc activity (kind = dns | conn | alloc). -> list[dict] |
identity(label, contact_email=...) |
Allocate your own /128; release with identity(release=True, address=...). -> dict |
agent(agent_or_address) |
One agent's detail + counters (id, or a /128, anything with : is an address). -> dict |
revoke(agent) |
Fully revoke an agent (irreversible). -> dict |
Keyless / serverless (no key, no CLI)
The graph read verbs plus verify, verify_details, rdap, and egress_ip are keyless: pure HTTPS, no whisper CLI, no key, so they run anywhere including serverless/edge functions:
from whisper_id import graph, verify, rdap
if verify(addr): # one HTTPS call; works in AWS Lambda, Cloudflare, etc.
print(rdap(addr)["name"])
print(graph().assess(addr)) # keyless threat posture, same anywhere
register, egress, and ip need the CLI (egress needs the local proxy; register needs your key).
Control plane: govern your fleet (no CLI)
import os
from whisper_id import identity, list_agents, policy, logs, agent, revoke
os.environ["WHISPER_API_KEY"] = "whisper_live_..." # or pass key=... to any call
a = identity("scout") # allocate a routable /128
policy(default="deny", allow=["api.github.com"]) # deny-by-default DNS policy
logs(agent=a["address"], kind="dns", from_="-1h") # recent activity
revoke(a["address"]) # irreversible
Every control call is one HTTPS POST of CALL whisper.agents({op, args}); it raises WhisperError with the server's message on failure. Set WHISPER_CONTROL_URL / WHISPER_RDAP_URL / WHISPER_FLOW_RUN_URL to point at a self-hosted deployment.
Requirements
For register / egress / ip, the whisper CLI on your PATH (this package is a thin, dependency-free wrapper over it). The graph read verbs and the keyless calls above need nothing.
curl -fsSL https://get.whisper.online | sh
Set WHISPER_API_KEY in the environment (or run whisper login) for the CLI-backed calls and the control plane (whisper.agents), which always needs it. Graph calls never require a key from this SDK: they are proxied to the graph with one or without, and the graph decides what to answer. A key lifts the keyless rate limit. verify and rdap are keyless too.
Links
- Site: https://whisper.online
- Docs: https://www.whisper.security/docs
- Query catalog: https://github.com/whisper-sec/whisper-catalog
- CLI: https://github.com/whisper-sec/whisper-cli
MIT licensed.
Metadata
Release files for whisper-id 0.7.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| whisper_id-0.7.0.tar.gz | 37.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| whisper_id-0.7.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 60.5 kB
Release files / whisper_id-0.7.0.tar.gz
| Download URL | whisper_id-0.7.0.tar.gz |
|---|---|
| Size | 37.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
845fe5851021a04f1f7802785c47f6082e7e2af4d21e067c2f5cb4c550efe12e
|
|
BLAKE2b-256 checksum How to use checksums |
808d8cabefe121eefa847ca5decc98a49fa9685eb903dea519091b9263ededa8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 2, 2026.
Transparency logRelease files / whisper_id-0.7.0-py3-none-any.whl
| Download URL | whisper_id-0.7.0-py3-none-any.whl |
|---|---|
| Size | 23.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5acc3b052efe6436d4278697fa5152a1c47be0021de66f638511960433ad890d
|
|
BLAKE2b-256 checksum How to use checksums |
d8cad504d874d7710631e666290c10ed7086f845bad59c5ec1f1a13c67a47480
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 2, 2026.
Transparency log