# Lookups Look up IP addresses and domains, and check your account and the API's health. ## lookup\_ip Returns intelligence for an IPv4 or IPv6 address, given as a string or an `ipaddress` object. ```python def lookup_ip(self, ip: str | IPv4Address | IPv6Address) -> IPInfo async def lookup_ip(self, ip: str | IPv4Address | IPv6Address) -> IPInfo ``` ```python info = client.lookup_ip("213.149.183.127") if info.location is not None: print(info.location.city, info.location.country) if info.intelligence is not None: for provider in info.intelligence.providers: print(provider.name, provider.type, provider.last_seen) ``` - **Cost:** Each lookup costs one credit, and failed lookups are not billed. - **Input:** An invalid address raises `ValueError`, and a value that is not a string or an `ipaddress` object raises `TypeError`, without sending a request. - **Errors:** Private and reserved addresses raise an `APIError` with status 422. ## lookup\_ips Looks up as many as 1000 addresses in one request. ```python def lookup_ips(self, ips: Iterable[str | IPv4Address | IPv6Address]) -> list[IPInfo] async def lookup_ips(self, ips: Iterable[str | IPv4Address | IPv6Address]) -> list[IPInfo] ``` ```python results = client.lookup_ips(["8.8.8.8", "1.1.1.1"]) for result in results: if result.intelligence is None: print(result.ip, "no intelligence available") continue print(result.ip, result.intelligence.risk_score) ``` - **Cost:** The API bills `ceil(n * 0.9)` credits for the `n` distinct usable addresses it looks up, and bills nothing if the account cannot cover the whole batch. - **Results:** Private and reserved addresses are skipped without a result and duplicates collapse into one, so there can be fewer results than addresses. Results follow the order in which each address first appears, but match them by `IPInfo.ip` rather than by position. - **Input:** An empty iterable returns `[]` without a request. An invalid address, or more than 1000 addresses, raises `ValueError` before anything is sent. - **Errors:** A batch in which every address is private or reserved raises an `APIError` with status 422. ## lookup\_domain Returns the honeypot traffic Synthient has observed for a domain. ```python def lookup_domain(self, domain: str, *, limit: int | None = None) -> DomainInfo async def lookup_domain(self, domain: str, *, limit: int | None = None) -> DomainInfo ``` - `limit` `int | None` — Caps the number of recent events returned, from 1 to 1000. `None` uses the API default of 50. A value below 1, or one that is not an integer, raises `ValueError`. ```python domain = client.lookup_domain("example.com", limit=10) print(domain.status, domain.stats.events_24h) for event in domain.recent_events: print(event.timestamp, event.target_subdomain, event.port) ``` - **Cost:** Each lookup costs one credit. - **Rate limit:** The API allows 20 per minute per team. - **Input:** Internationalized names must be sent in Punycode. An empty domain, `"."`, or `".."` raises `ValueError` without a request. - **Results:** A domain that has never been observed returns `status` `"inactive"` with zeroed stats rather than an error. ## Account and health ### account Returns the account that owns the API key, including its scopes and remaining lookup credits. ```python def account(self) -> Account async def account(self) -> Account ``` ```python from datetime import timedelta account = client.account() print(account.email, account.organization.name, account.scopes) quota = account.lookup_quota print(quota.credits, "credits, resetting in", timedelta(seconds=quota.resets_in)) ``` - **Cost:** It costs no credits. ### health Returns `None` if the API and its dependencies are healthy. ```python def health(self) -> None async def health(self) -> None ``` ```python client.health() ``` - **Cost:** It costs no credits. - **Errors:** An unhealthy API raises an `APIError` with status 503, which is not retried. The API does not check the key, but a malformed key raises an `APIError` with status 401.