Lookups

View as markdown

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.

def lookup_ip(self, ip: str | IPv4Address | IPv6Address) -> IPInfo

async def lookup_ip(self, ip: str | IPv4Address | IPv6Address) -> IPInfo
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.

def lookup_ips(self, ips: Iterable[str | IPv4Address | IPv6Address]) -> list[IPInfo]

async def lookup_ips(self, ips: Iterable[str | IPv4Address | IPv6Address]) -> list[IPInfo]
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.

def lookup_domain(self, domain: str, *, limit: int | None = None) -> DomainInfo

async def lookup_domain(self, domain: str, *, limit: int | None = None) -> DomainInfo
  • Name
    limit
    Type
    int | None
    Description

    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.

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.

def account(self) -> Account

async def account(self) -> Account
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.

def health(self) -> None

async def health(self) -> None
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.