Skip to content

Search Tools API

The Netlas Search Tools API allows you to perform search queries across any Netlas data collection from third-party applications and scripts.

Before you start building your integration, you need to decide which data you want to retrieve. It's important to distinguish between retrieving data for individual hosts and data for search queries.

  • Host API: This specific endpoint in the Netlas API is designed to retrieve detailed information about a single IP address or domain. It is ideal for targeted queries where aggregated data about a particular host is required.
  • Search Query API: This set of endpoints within the Netlas API enables complex and diverse queries across different parameters and data indices. These endpoints are suited for broader searches, utilizing Search Query Language.
Host API Search Query API
Search by IP or domain only Complex queries with various search arguments
Returns specific data for an IP or domain Returns multiple results based on the query
Aggregated data from all data collections Specific data from chosen data collections or indices
Corresponds to IP/Domain Info tool Corresponds to Responses, DNS, WHOIS, SSL certs search

Use Cases for Host API

  • SIEM System Plugin: Automatically enrich security logs in any SIEM system with detailed IP/domain data from Netlas, providing contextual insights for security events.
  • Firewall Management Plugin: Develop a plugin for firewall solutions that utilizes Netlas data to automate security rule creation based on IP reputation, known threats, and geolocation.
  • CMS Plugin: Identify a site visitor’s company by IP address for lead generation, analytics, or content adaptation.
  • Remote Employee Compliance Checks: Verify the IP address information of remote employees to ensure they connect from secure locations.
  • Browser Plugin: Access security-related information about web resources and their hosting servers while browsing.

Use Cases for Search Query API

  • Reconnaissance Script: Automate the collection and analysis of external attack surface data to understand the target environment.
  • Non-intrusive Assessment: Gather information about a system's security posture without direct interaction, avoiding detection.
  • OSINT Profiling: Collect publicly available information to create detailed profiles of organizations or systems.
  • Malware Resources Search: Identify and track potential threats by searching for online resources that host malware.
  • Phishing Resources Search: Identify and analyze websites used in phishing campaigns to help protect against fraudulent activities.

Host API

You can fetch data for any valid IP address or domain name. This API endpoint aggregates data from all available data collections related to the queried IP or domain.

Query using CLI tool
# IP query example
netlas host "135.125.237.168" --apikey "YOUR_API_KEY" --format json

# Domain query example
netlas host "app.netlas.io" --apikey "YOUR_API_KEY" --format json
Query using curl utility
# IP query example
curl -X 'GET' 'https://app.netlas.io/api/host/135.125.237.168/' \
  -H 'X-API-Key: YOUR_API_KEY'

# Domain query example
curl -X 'GET' 'https://app.netlas.io/api/host/app.netlas.io/' \
  -H 'X-API-Key: YOUR_API_KEY'
Query using Python SDK
import netlas
import json

# Define the API key and initialize the Netlas client
netlas_connection = netlas.Netlas(api_key="YOUR_API_KEY")

# IP query example
ip_info = netlas_connection.host(host="135.125.237.168")
print(json.dumps(ip_info))

# Domain query example
domain_info = netlas_connection.host(host="app.netlas.io")
print(json.dumps(domain_info))

A JSON document with the following data will be returned:

IP query Domain query
PTR, Subnet and Autonomous system data DNS records
Geolocation Related domains
Privacy flags (VPN/Tor/Proxy) Domain WHOIS data
Organization Threat intelligence data
IP WHOIS data Exposed WEB ports & software, including detected CVE
Related domains
Threat intelligence data
Exposed ports & software, including detected CVE
JSON documents with IP/domain information
{
  "ip": "135.125.237.168",
  "type": "ip",  
  "privacy": {
    "is_vpn": false,
    "is_proxy": false,
    "is_tor": false
  },
  "organization": "OVH GmbH",
  "geo": {
    "continent": "Europe",
    "country": "France",
    "location": {
      "latitude": 48.8582,
      "accuracy_radius": 500,
      "time_zone": "Europe/Paris",
      "longitude": 2.3387
    },
    "registered_country": "France"
  },
  "domains_count": 3,
  "domains": [
    "mail.netlas.io",
    "app.netlas.io",
    "pay.netlas.io"
  ],
  "software": [
    {
      "tag": [
        {
          "debian": {
            "version": ""
          },
          "name": "debian",
          "cpe": [
            "cpe:/o:debian:debian_linux"
          ],
          "description": "Debian is a Linux software which is a free open-source software.",
          "fullname": "Debian",
          "category": [
            "Operating systems"
          ]
        },
        {
          "name": "dovecot",
          "cpe": [
            "cpe:/a:dovecot:dovecot"
          ],
          "dovecot": {
            "version": ""
          },
          "description": "Dovecot is an open-source IMAP and POP3 server for Unix-like operating systems, written primarily with security in mind",
          "fullname": "Dovecot",
          "category": [
            "Mail server"
          ]
        }
      ],
      "uri": "imaps://135.125.237.168:993"
    },
    {
      "tag": [
        {
          "nginx": {
            "version": ""
          },
          "name": "nginx",
          "cpe": [
            "cpe:/a:nginx:nginx"
          ],
          "description": "Nginx is a web server that can also be used as a reverse proxy, load balancer, mail proxy and HTTP cache.",
          "fullname": "Nginx",
          "category": [
            "Web servers",
            "Reverse proxies"
          ]
        }
      ],
      "uri": "https://135.125.237.168:443/"
    },
    {
      "tag": [
        {
          "debian": {
            "version": ""
          },
          "name": "debian",
          "cpe": [
            "cpe:/o:debian:debian_linux"
          ],
          "description": "Debian is a Linux software which is a free open-source software.",
          "fullname": "Debian",
          "category": [
            "Operating systems"
          ]
        },
        {
          "name": "dovecot",
          "cpe": [
            "cpe:/a:dovecot:dovecot"
          ],
          "dovecot": {
            "version": ""
          },
          "description": "Dovecot is an open-source IMAP and POP3 server for Unix-like operating systems, written primarily with security in mind",
          "fullname": "Dovecot",
          "category": [
            "Mail server"
          ]
        }
      ],
      "uri": "imapstarttls://135.125.237.168:143"
    }
  ],
  "ptr": [
    "mail.netlas.io"
  ],
  "whois": {
    "abuse": "[email protected]",
    "ip": {
      "gte": "135.125.236.0",
      "lte": "135.125.237.255"
    },
    "related_nets": [
      {
        "created": "2020-11-03T13:34:30Z",
        "start_ip": "135.125.128.0",
        "range": "135.125.128.0 - 135.125.255.255",
        "cidr": [
          "135.125.128.0/17"
        ],
        "net_size": 32767,
        "updated": "2020-11-03T13:34:30Z",
        "end_ip": "135.125.255.255"
      }
    ],
    "net": {
      "country": "DE",
      "address": "St. Johanner Str. 41-43\n66111 Saarbrucken\nDeutschland",
      "created": "2021-03-30T07:33:51Z",
      "range": "135.125.236.0 - 135.125.237.255",
      "handle": "OTC13-RIPE",
      "organization": "OVH GmbH",
      "name": "VPS-DE2",
      "start_ip": "135.125.236.0",
      "cidr": [
        "135.125.236.0/23"
      ],
      "net_size": 511,
      "updated": "2021-03-30T07:33:51Z",
      "end_ip": "135.125.237.255",
      "contacts": {
        "emails": [
          "[email protected]"
        ]
      }
    },
    "asn": {
      "number": [
        "16276"
      ],
      "registry": "ripencc",
      "country": "FR",
      "name": "OVH",
      "cidr": "135.125.128.0/17",
      "updated": "2000-10-11"
    }
  },
  "ports": [
    {
      "prot4": "tcp",
      "protocol": "imaps",
      "port": 993,
      "prot7": "imap"
    },
    {
      "prot4": "tcp",
      "protocol": "smtp",
      "port": 25,
      "prot7": "smtp"
    },
    {
      "prot4": "tcp",
      "protocol": "https",
      "port": 443,
      "prot7": "http"
    },
    {
      "prot4": "tcp",
      "protocol": "imapstarttls",
      "port": 143,
      "prot7": "imap"
    },
    {
      "prot4": "tcp",
      "protocol": "smtps",
      "port": 465,
      "prot7": "smtp"
    },
    {
      "prot4": "tcp",
      "protocol": "smtpstarttls",
      "port": 25,
      "prot7": "smtp"
    }
  ]
}
{
  "domain": "app.netlas.io",
  "type": "domain",
  "related_domains_count": 4,
  "related_domains": [
    "netlas.io",
    "pay.netlas.io",
    "www.netlas.io",
    "mail.netlas.io"
  ],
  "dns": {
    "a": [
      "135.125.237.168"
    ],
    "zone": "io",
    "level": 3
  },
  "whois": {
    "server": "whois.namecheap.com",
    "extension": "io",
    "last_updated": "2023-10-25T10:53:05.536Z",
    "registrar": {
      "phone": "+1.9854014545",
      "referral_url": "http://www.namecheap.com",
      "name": "NAMECHEAP INC",
      "id": "1068",
      "email": "[email protected]"
    },
    "technical": {
      "country": "IS",
      "province": "Capital Region",
      "city": "Reykjavik",
      "phone": "+354.4212434",
      "street": "Kalkofnsvegur 2",
      "organization": "Privacy service provided by Withheld for Privacy ehf",
      "name": "Redacted for Privacy",
      "postal_code": "101",
      "email": "[email protected]"
    },
    "whois_server": "whois.namecheap.com",
    "level": 2,
    "name_servers": [
      "pdns1.registrar-servers.com",
      "pdns2.registrar-servers.com"
    ],
    "expiration_date": "2026-11-06T08:10:14.820Z",
    "punycode": "netlas.io",
    "zone": "io",
    "stats": {
      "retries": 0,
      "quota_retries": 0,
      "parser": "no_error",
      "was_queued": false,
      "total_time": 5661529222,
      "error": "no_error"
    },
    "administrative": {
      "country": "IS",
      "province": "Capital Region",
      "city": "Reykjavik",
      "phone": "+354.4212434",
      "street": "Kalkofnsvegur 2",
      "organization": "Privacy service provided by Withheld for Privacy ehf",
      "name": "Redacted for Privacy",
      "postal_code": "101",
      "email": "[email protected]"
    },
    "domain": "netlas.io",
    "name": "netlas",
    "id": "1f378468bddb44f3a084571e1e028e55-DONUTS",
    "created_date": "2020-11-06T08:10:14.820Z",
    "registrant": {
      "country": "IS",
      "province": "Capital Region",
      "city": "Reykjavik",
      "phone": "+354.4212434",
      "street": "Kalkofnsvegur 2",
      "organization": "Privacy service provided by Withheld for Privacy ehf",
      "name": "Redacted for Privacy",
      "postal_code": "101",
      "email": "[email protected]"
    },
    "updated_date": "2022-09-28T09:38:39.210Z",
    "extracted_domain": "netlas.io",
    "status": [
      "clienttransferprohibited"
    ]
  },
  "software": [
    {
      "tag": [
        {
          "django": {
            "version": ""
          },
          "name": "django",
          "cpe": [
            "cpe:/a:djangoproject:django"
          ],
          "description": "Django is a Python-based free and open-source web application framework.",
          "fullname": "Django",
          "category": [
            "Web frameworks"
          ]
        },
        {
          "google_font_api": {
            "version": ""
          },
          "name": "google_font_api",
          "description": "Google Font API is a web service that supports open-source font files that can be used on your web designs.",
          "fullname": "Google Font API",
          "category": [
            "Font scripts"
          ]
        },
        {
          "nginx": {
            "version": ""
          },
          "name": "nginx",
          "cpe": [
            "cpe:/a:nginx:nginx"
          ],
          "description": "Nginx is a web server that can also be used as a reverse proxy, load balancer, mail proxy and HTTP cache.",
          "fullname": "Nginx",
          "category": [
            "Web servers",
            "Reverse proxies"
          ]
        }
      ],
      "uri": "https://app.netlas.io:443/"
    }
  ],
  "ports": [
    {
      "prot4": "tcp",
      "protocol": "https",
      "port": 443,
      "prot7": "http"
    }
  ],
  "related_domains_query": "(domain:*.netlas.io AND NOT domain:app.netlas.io) OR domain:netlas.io"
}

Filtering Output

You can limit the amount of output data by using the include option to specify a list of fields to be included in the response, or exclude to filter out certain fields. However, both options cannot be used simultaneously in the same request.

Filtering the output with query options
netlas host "google.com" \
  --format json \
  --include "whois.registrant.organization" \
  --apikey "YOUR_API_KEY"
{"whois": {"registrant": {"organization": "Google LLC"}}}
Filtering the output with query options
curl -X 'GET' -s \
'https://app.netlas.io/api/host/google.com/?fields=whois.registrant.organization&source_type=include' \
  -H 'X-API-Key: YOUR_API_KEY'
{"whois": {"registrant": {"organization": "Google LLC"}}}
Filtering the output with query options
import netlas
import json

# Define the API key and initialize the Netlas client
netlas_connection = netlas.Netlas(api_key="YOUR_API_KEY")

domain_info = netlas_connection.host(
  host="google.com", 
  fields="whois.registrant.organization"
)

print(json.dumps(domain_info))
{"whois": {"registrant": {"organization": "Google LLC"}}}

Use jq utility to filter CLI tool and curl output.

Filtering the output with jq utility
netlas host "8.8.8.8" -f json -a "YOUR_API_KEY" | jq -r '.ptr[0]'
dns.google
Filtering the output with jq utility
curl -X 'GET' -s \
  'https://app.netlas.io/api/host/8.8.8.8/' \
  -H 'X-API-Key: YOUR_API_KEY' \
| jq -r '.ptr[0]'
dns.google
Filtering the output
import netlas

# Define the API key and initialize the Netlas client
netlas_connection = netlas.Netlas(api_key="YOUR_API_KEY")

ip_info = netlas_connection.host(host="8.8.8.8")
print(ip_info['ptr'][0])
dns.google

Both filtering methods can also be used for the Search Query API.

YAML Output

By default, the CLI tool outputs data in YAML format, which is more readable for humans. Additionally, Netlas employs the Pygments library for YAML syntax highlighting. However, using YAML in automation scripts is not recommended for two main reasons:

  1. Complex Data Parsing: Extracting specific data from YAML is more challenging compared to JSON due to its structured complexity.
  2. Additional Formatting Handling: ANSI escape codes used for formatting and color in the CLI output must be filtered out, adding an extra step in data processing.

This is why we use the --format json option with the CLI tool in the examples provided. If you still need to utilize YAML output, you can strip ANSI escape characters using the sed utility:

Filtering CLI tool formated YAML output
netlas count "host:135.125.237.168" | sed 's/\x1b\[[0-9;]*m//g'
Explanation:

  • \x1b: This is the escape character, indicating the start of an ANSI sequence.
  • \[[0-9;]*m: This pattern matches the sequence starting with [, followed by any combination of digits and semicolons, and ending with m, which is typical for color and style escape codes.

Search Query API

Each Netlas Search tool is associated with a set of API endpoints that facilitate different search operations:

  • /api/[search_tool]/ — This endpoint allows you to make search queries, returning up to 20 results per request.
  • /api/[search_tool]/download/ — Fetches search results in a stream format. You must specify the size of the stream as a parameter in the request.
  • /api/[search_tool]_count/ — Calculates the total number of results for a specific query. If there are fewer than 1000 results, the method returns the exact count. For counts exceeding 1000 results, the number is estimated with a small margin of error not exceeding 3%.
  • /api/[search_tool]_facet/ — Enables facet searches, which organize search results into groups based on specified criteria.

A complete list of available endpoints is detailed in the API specification.

Regardless of the specific search tool being used, endpoints of the same category exhibit consistent behavior. Both the Netlas SDK and the CLI Tool provide corresponding methods and commands to interact with these endpoints effectively.

Responses available for the query, Search command
#!/bin/bash

# Define the API key
API_KEY="YOUR_API_KEY"

# Define the query
QUERY="host:135.125.237.168"

# Get the count of responses for the given query
COUNT=$(netlas count "$QUERY" --apikey "$API_KEY" --format json \
    | jq ".count")

# Check if there are any responses and search for them if there are
if [ "$COUNT" -gt 0 ]; then
    echo "Responses for $QUERY"

    # Calculate the number of pages needed based on 20 results per page
    NUM_PAGES=$((($COUNT + 19) / 20))

    # Loop through each page and fetch the results
    for ((PAGE=0; PAGE<NUM_PAGES; PAGE++)); do
        RESULTS=$(netlas search "$QUERY" --datatype "response" \
            --page "$PAGE" --include "uri" \
            --apikey "$API_KEY" --format json)

        # Check if items exist in the results and print URIs
        if [[ $RESULTS == *"items"* ]]; then
            echo "$RESULTS" | jq -r '.items[].data.uri'
        fi

        # Wait for a second to avoid throttling
        sleep 1
    done
else
    echo "No responses found."
fi
Responses for host:135.125.237.168
smtpstarttls://135.125.237.168:25
smtps://135.125.237.168:465
https://135.125.237.168:443/
imap://135.125.237.168:143
imaps://135.125.237.168:993
smtp://135.125.237.168:25
http://135.125.237.168:80/
imapstarttls://135.125.237.168:143
Responses available for the query, Search endpoint
#!/bin/bash

# Define the API key and the base URL for the Netlas API
API_KEY="YOUR_API_KEY"
BASE_URL="https://app.netlas.io/api"

# Define the query
QUERY="host:135.125.237.168"

# Get the count of responses for the given query
COUNT=$(curl -s -H "X-API-Key: $API_KEY" \
    "$BASE_URL/responses_count/?q=$QUERY" \
    | jq ".count")

# Check if there are any responses and search for them if there are
if [ "$COUNT" -gt 0 ]; then
    echo "Responses for $QUERY"

    # Calculate the number of pages needed based on 20 results per page
    NUM_PAGES=$((($COUNT + 19) / 20))

    # Loop through each page and fetch the results
    for ((PAGE=0; PAGE<NUM_PAGES; PAGE++)); do
        OFFSET=$(($PAGE*20))
        RESULTS=$(curl -s -H "X-API-Key: $API_KEY" \
            "$BASE_URL/responses/?q=$QUERY&start=$OFFSET&fields=uri" \
            | jq -r ".items[].data.uri")

        # Check if items exist in the results and print URIs
        if [ -n "$RESULTS" ]; then
            echo "$RESULTS"
        fi

        # Wait for a second to avoid throttling
        sleep 1
    done
else
    echo "No responses found."
fi
Responses for host:135.125.237.168
smtpstarttls://135.125.237.168:25
smtps://135.125.237.168:465
https://135.125.237.168:443/
imap://135.125.237.168:143
imaps://135.125.237.168:993
smtp://135.125.237.168:25
http://135.125.237.168:80/
imapstarttls://135.125.237.168:143
Responses available for the query, Search method
import netlas
import time

# Define the API key and initialize the Netlas client
netlas_connection = netlas.Netlas(api_key='YOUR_API_KEY')

# Define the query
query = "host:135.125.237.168"

# Get the count of responses for the given query
cnt_of_res = netlas_connection.count(query=query, datatype='response')

# Check if there are any responses and search for them if there are
if cnt_of_res['count'] > 0:
    print("Responses for " + query)

    # Calculate the number of pages needed based on 20 results per page
    num_pages = (cnt_of_res['count'] + 19) // 20  # rounding up

    # Loop through each page and fetch the results
    for page in range(num_pages):
        search_results = netlas_connection.search(
          query=query, datatype='response', page=page, fields='uri'
          )

        if 'items' in search_results:
            # iterate over data and print: IP address, port, path and protocol
            for response in search_results['items']:
                print(response['data']['uri'])

        # wait for a second to avoid throtling
        time.sleep(1)
else:
    print("No responses found.")
Responses for host:135.125.237.168
smtpstarttls://135.125.237.168:25
smtps://135.125.237.168:465
https://135.125.237.168:443/
imap://135.125.237.168:143
imaps://135.125.237.168:993
smtp://135.125.237.168:25
http://135.125.237.168:80/
imapstarttls://135.125.237.168:143

Search vs. Download

The Search and Download methods in the Netlas API cater to different needs, primarily based on the volume of data required:

  • Search Method: Depending on your pricing plan, you can retrieve between 200 and 4000 search results. This method requires making a separate request for every 20 results, which is ideal for situations where pagination is needed or when the expected number of results is relatively low. This approach is used in our web application to support user interfaces that include pagination.

  • Download Method: Unlike the Search method, the Download method is not technically limited to a specific number of results per request. Instead, it is governed only by your pricing plan, allowing for the retrieval of large data sets in a single request. This method is particularly useful for handling extensive search queries that yield a large number of results, eliminating the need for repeated requests with incremental offsets. The Netlas SDK and CLI tool additionally include a download_all() method and an --all key that allow you to query all available results.

Here's an example illustrating the simplicity and conciseness of using the Download method:

Responses available for the query, download command
#!/bin/bash

# Define the API key
API_KEY="YOUR_API_KEY"

# Define the query
QUERY="host:*.netlas.io"

# Download all available responses
netlas download "$QUERY" --all --include "uri" --apikey "$API_KEY"\
  | jq -r ".data.uri"
Responses for host:*.netlas.io
http://pay.netlas.io:80/
https://pay.netlas.io:443/login
https://pay.netlas.io:443/
http://mail.netlas.io:80/
https://mail.netlas.io:443/
http://app.netlas.io:80/
https://app.netlas.io:443/
Responses available for the query, download endpoint
#!/bin/bash

# Define the API key and the base URL for the Netlas API
API_KEY="YOUR_API_KEY"
BASE_URL="https://app.netlas.io/api"

# Define the query
QUERY="host:*.netlas.io"

# Get the count of responses for the given query
COUNT=$(curl -s -H "X-API-Key: $API_KEY" \
    "$BASE_URL/responses_count/?q=$QUERY" \
    | jq ".count")

# Check if there are any responses and download them if there are
if [ "$COUNT" -gt 0 ]; then
    curl -s -X POST "$BASE_URL/responses/download/" \
     -H "Content-Type: application/json" \
     -H "X-API-Key: $API_KEY" \
     -d '{
          "q": "'"${QUERY}"'",
          "fields": ["uri"],
          "source_type": "include",
          "size": '"${COUNT}"'
         }' | jq -r ".[].data.uri"
fi
Responses for host:*.netlas.io
http://pay.netlas.io:80/
https://pay.netlas.io:443/login
https://pay.netlas.io:443/
http://mail.netlas.io:80/
https://mail.netlas.io:443/
http://app.netlas.io:80/
https://app.netlas.io:443/
Responses available for the query, download_all() method
import netlas
import json

# Define the API key and initialize the Netlas client
netlas_connection = netlas.Netlas(api_key='YOUR_API_KEY')

# Define the query
query = "host:*.netlas.io"

# Download all available responses
for resp in netlas_connection.download_all(query):
    # decode from binary stream
    response = json.loads(resp.decode('utf-8'))
    print(response['data']['uri'])
Responses for host:*.netlas.io
http://pay.netlas.io:80/
https://pay.netlas.io:443/login
https://pay.netlas.io:443/
http://mail.netlas.io:80/
https://mail.netlas.io:443/
http://app.netlas.io:80/
https://app.netlas.io:443/

The code utilizing the Download method is simpler and more concise because it eliminates the need for looped requests with offsets; all results are obtained in a single request.

How to download large sets of search results that exceed download limits? Read the FAQ →

Indices

By default, queries target the most up-to-date index. However, other indices may also be available to you. Utilizing indices through the API is analogous to using indices in the web application.

Available responses indices
netlas indices --format json --apikey "YOUR_API_KEY" \
    | jq -r '.[] | select(.type | contains("responses")) | "\(.id): \(.name)"' \
    | sort -g
83: responses-2022-09-21
84: responses-2023-01-03
86: responses_*_2023-05-11
89: responses_*_2023-09-22
103: responses_*_2024-01-22
110: responses_*_2024-04-25
Available responses indices
#!/bin/bash

# Define the API key and the base URL for the Netlas API
API_KEY="YOUR_API_KEY"
BASE_URL="https://app.netlas.io/api"

# Fetch the indices and filter the results
curl -s -H "X-API-Key: $API_KEY" "$BASE_URL/indices/" \
    | jq -r '.[] | select(.type | contains("responses")) | "\(.id): \(.name)"' \
    | sort -g
83: responses-2022-09-21
84: responses-2023-01-03
86: responses_*_2023-05-11
89: responses_*_2023-09-22
103: responses_*_2024-01-22
110: responses_*_2024-04-25
Available responses indices
import netlas

# Define the API key and initialize the Netlas client
api_key = 'YOUR_API_KEY'
netlas_connection = netlas.Netlas(api_key=api_key)

# Fetch the indices
indices = netlas_connection.indices()

# Filter indices where the type contains 'responses' and sort by id
filtered_indices = [index for index in indices if 'responses' in index['type']]
sorted_indices = sorted(filtered_indices, key=lambda x: x['id'])

# Print the results
for index in sorted_indices:
    print(f"{index['id']}: {index['name']}")
83: responses-2022-09-21
84: responses-2023-01-03
86: responses_*_2023-05-11
89: responses_*_2023-09-22
103: responses_*_2024-01-22
110: responses_*_2024-04-25

One of the examples provided below demonstrates how to perform a historical search and compare results from different indices.

Examples

The examples below assume that the API key has been saved by the user, as described in the SDK installation instructions.

IP to Company

In the example below, the Host API is used to query for the organization name and country associated with IP addresses.

ip2company-cli.sh
#!/bin/bash

# Check if an IP address is provided
if [ "$#" -ne 1 ]; then
    echo "Usage: $0 <IP_ADDRESS>"
    exit 1
fi

# Make the API call using CLI tool, assuming that API key is saved
response=$(netlas host "$1" --format json)

# Extract organization and country using jq
organization=$(echo $response | jq -r '.organization // "n/a"')
country=$(echo $response | jq -r '.geo.country // "n/a"')

# Conditional output based on data availability
if [ "$organization" = "n/a" ] && [ "$country" = "n/a" ]; then
    echo "No organization information found for the IP: $1"
else
    echo "$organization, $country"
fi
~ # bash ip2company-cli.sh 8.8.8.8        
Google LLC (GOGL), United States
ip2company-curl.sh
#!/bin/bash

# Check if an IP address is provided
if [ "$#" -ne 1 ]; then
    echo "Usage: $0 <IP_ADDRESS>"
    exit 1
fi

# Define the API key and the URL for query
API_KEY="YOUR_API_KEY"
API_URL="https://app.netlas.io/api/host/$1/"

# Make the API call using curl with the API key in the header
response=$(curl -s -H "X-API-Key: $API_KEY" "$API_URL")

# Extract organization and country using jq
organization=$(echo $response | jq -r '.organization // "n/a"')
country=$(echo $response | jq -r '.geo.country // "n/a"')

# Conditional output based on data availability
if [ "$organization" = "n/a" ] && [ "$country" = "n/a" ]; then
    echo "No organization information found for the IP: $1"
else
    echo "$organization, $country"
fi
~ # bash ip2company-curl.sh 8.8.8.8        
Google LLC (GOGL), United States
ip2company.py
import sys
import netlas

# Check if an IP address is provided
if len(sys.argv) < 2:
    print("Usage: python ip2company.py <IP_ADDRESS>")
    sys.exit(1)

# Initialize the Netlas API assuming that API key is saved
api_key = netlas.helpers.get_api_key()
netlas_api = netlas.Netlas(api_key=api_key)

# Make the API call to get host information
try:
    response = netlas_api.host(host=sys.argv[1])

    # Check if the response contains the necessary data
    if response and 'organization' in response:
        organization_name = response.get('organization', 'n/a')
        country = response.get('geo', {}).get('country', 'n/a')
        # Default to 'n/a' if not available

        print(f"{organization_name}, {country}")
    else:
        print("No organization information found for the IP:", sys.argv[1])
except Exception as e:
    print("An error occurred:", str(e))
~ # python ip2company.py 8.8.8.8        
Google LLC (GOGL), United States

Abuse Contact

This script retrieves the abuse contact for a domain or IP address using the Host API. For domains, it uses the registrar's email, and for IP addresses, it retrieves the abuse email from the IP WHOIS data.

Please note, data availability depends on your pricing plan

If your pricing plan does not include access to contact information such as phone numbers and email addresses, this example will not be applicable.

abuse_contact-cli.sh
#!/bin/bash

# Check if a domain or IP address is provided
if [ "$#" -ne 1 ]; then
    echo "Usage: $0 <DOMAIN_OR_IP>"
    exit 1
fi

# Make the API call using CLI tool, assuming that API key is saved
response=$(netlas host $1 --format json)

# Determine the type and extract the relevant abuse contact
if echo "$response" | jq -e '.ip' > /dev/null; then
    abuse_contact=$(echo "$response" | jq -r '.whois.abuse // "n/a"')
elif echo "$response" | jq -e '.domain' > /dev/null; then
    abuse_contact=$(echo "$response" | jq -r '.whois.registrar.email // "n/a"')
else
    abuse_contact="n/a"
fi

echo "$abuse_contact"
~ # bash abuse_contact-cli.sh dns.google
[email protected]
~ # bash abuse_contact-cli.sh 8.8.8.8   
[email protected]
abuse_contact-curl.sh
#!/bin/bash

# Check if a domain or IP address is provided
if [ "$#" -ne 1 ]; then
    echo "Usage: $0 <DOMAIN_OR_IP>"
    exit 1
fi

# Define the API key and the URL for query
API_KEY="YOUR_API_KEY"
API_URL="https://app.netlas.io/api/host/$1/"

# Make the API call using curl with the API key in the header
response=$(curl -s -H "X-API-Key: $API_KEY" "$API_URL")

# Determine the type and extract the relevant abuse contact
if echo "$response" | jq -e '.ip' > /dev/null; then
    abuse_contact=$(echo "$response" | jq -r '.whois.abuse // "n/a"')
elif echo "$response" | jq -e '.domain' > /dev/null; then
    abuse_contact=$(echo "$response" | jq -r '.whois.registrar.email // "n/a"')
else
    abuse_contact="n/a"
fi

echo "$abuse_contact"
~ # bash abuse_contact-curl.sh dns.google
[email protected]
~ # bash abuse_contact-curl.sh 8.8.8.8   
[email protected]
abuse_contact.py
import sys
import netlas

# Check if a domain or IP address is provided
if len(sys.argv) < 2:
    print("Usage: python abuse_contact.py <DOMAIN_OR_IP>")
    sys.exit(1)

# Initialize the Netlas API, assuming that API key is saved
api_key = netlas.helpers.get_api_key()
netlas_api = netlas.Netlas(api_key=api_key)

# Make the API call to get host information
try:
    response = netlas_api.host(host=sys.argv[1])

    # Determine if the identifier is a domain or IP and extract the relevant abuse contact
    if 'domain' in response and 'whois' in response:
        if 'registrar' in response['whois'] and 'email' in response['whois']['registrar']:
            abuse_contact = response['whois']['registrar']['email']
        else:
             abuse_contact = 'n/a'
    elif 'ip' in response and 'whois' in response:
        abuse_contact = response['whois']['abuse'] if 'abuse' in response['whois'] else 'n/a'
    else:
        abuse_contact = 'n/a'

    print(abuse_contact)
except Exception as e:
    print("An error occurred:", str(e))
~ # python abuse_contact.py dns.google
[email protected]
~ # python abuse_contact.py 8.8.8.8   
[email protected]

Subdomain Enumeration

This example leverages the Search Query API to access the DNS Registry data collection. It performs a search based on user input and downloads all available results.

subdomains-cli.sh
#!/bin/bash

# Check if a domain seqrch query is provided
if [ "$#" -ne 1 ]; then
    printf "Usage:\n\t$0 \'<*.domain.com>\'\n\t$0 \'<domain.*>\'\n"
    exit 1
fi

# Download all available results
netlas download "domain:$1" --all --datatype domain --include "domain" \
    | jq -r ".data.domain"
~ # bash subdomains-cli.sh "*.netlas.io"
app.netlas.io
pay.netlas.io
www.netlas.io
mail.netlas.io
subdomains-curl.sh
#!/bin/bash

# Check if a domain seqrch query is provided
if [ "$#" -ne 1 ]; then
    printf "Usage:\n\t$0 \'<*.domain.com>\'\n\t$0 \'<domain.*>\'\n"
    exit 1
fi

# Define the API key and the base URL for the Netlas API
API_KEY="YOUR_API_KEY"
BASE_URL="https://app.netlas.io/api"

# Get the count of domains for the given query
COUNT=$(curl -s -H "X-API-Key: $API_KEY" \
    "${BASE_URL}/domains_count/?q=domain:$1" | jq ".count")

# Check if there are any results and download them if there are
if [ "$COUNT" -gt 0 ]; then
    curl -s -X POST "$BASE_URL/domains/download/" \
        -H "Content-Type: application/json" \
        -H "X-API-Key: $API_KEY" \
        -d '{
            "q": "domain:'"${1}"'",
            "fields": ["domain"],
            "source_type": "include",
            "size": '"${COUNT}"'
            }' | jq -r ".[].data.domain"
fi
~ # bash subdomains-curl.sh "*.netlas.io"
app.netlas.io
pay.netlas.io
www.netlas.io
mail.netlas.io
subdomains.py
import sys
import netlas
import json

# Check if a domain seqrch query is provided
if len(sys.argv) != 2:
    print("Usage:\n" +
      "\tpython subdomains.py '<*.domain.com>'\n" +
      "\tpython subdomains.py '<domain.*>'"
      )
    sys.exit(1)

# Initialize the Netlas API assuming that API key is saved
api_key = netlas.helpers.get_api_key()
netlas_api = netlas.Netlas(api_key=api_key)

# Define the query
query = "domain:" + sys.argv[1]

try:
    # Download all available results
    for resp in netlas_api.download_all(
        query=query, 
        datatype='domain',
        fields='domain'
    ):
        # decode from binary stream
        response = json.loads(resp.decode('utf-8'))
        print(response['data']['domain'])
except Exception as e:
    print("An error occurred:", str(e))
~ # python subdomains.py "*.netlas.io"
app.netlas.io
pay.netlas.io
www.netlas.io
mail.netlas.io

Always choose the download method to fetch a large amount of results.

This example uses the Download method to accommodate potentially large datasets that could exceed the 4000 result limit imposed by the Search method.

This script takes a domain as input, queries Domain WHOIS data for the organization name of a registrant, and searches for domains with the same organization name. If the number of search results exceeds 1,000, user confirmation is required to proceed with the download. Use -f or --force to bypass the confirmation dialogue.

related_domains-cli.sh
#!/bin/bash

# Check if a domain is provided
if [ "$#" -lt 1 ]; then
    echo "Usage: $0 <DOMAIN> [-f|--force]"
    echo "    <DOMAIN> - the domain to query"
    echo "    -f, --force - force downloading all results without prompt, even if results are more than 1000"
    exit 1
fi

force_download=false
if [ "$#" -eq 2 ] && ([[ "$2" == "-f" ]] || [[ "$2" == "--force" ]]); then
    force_download=true
fi

# Fetch organization name from WHOIS data
organization=$(netlas search "domain:$1" \
        --format json --datatype whois-domain \
        | jq -r '.items[0].data.registrant.organization')

if [ "$organization" == "null" ] || [ -z "$organization" ]; then
    echo "No organization found for the domain."
    exit 1
fi

# Create query
domains_query="registrant.organization:\"$organization\""

#Count domains with the same organization name
count=$(netlas count "$domains_query" \
        --format json --datatype whois-domain | jq ".count")

# Check if there are any results and optionally ask for confirmation
if [ "$count" -eq 0 ]; then
    echo "No related domains found."
    exit 1
elif [ "$count" -gt 1000 ] && ! $force_download; then
    echo "Found $count results for organization: $organization."
    read -p "Do you really want to download all results? (Y/n): " user_input
    if [[ "$user_input" != "Y" && "$user_input" != "y" ]]; then
        echo "Download canceled."
        exit 0
    fi
fi

# Fetching domains registered to the same organization
netlas download "$domains_query"  \
        --datatype whois-domain --include "domain" \
        --count "$count" \
        | jq -r ".data.domain"
~ # bash related_domains-cli.sh microsoft.com 
Found 33304 results for organization: Microsoft Corporation.
Do you really want to download all results? (Y/n): Y
halo5.fans
session-verify-user.info
ndclient.net
indiaappfest.com
decloudapp.com
...
related_domains-curl.sh
#!/bin/bash

# Check if a domain is provided
if [ "$#" -lt 1 ]; then
    echo "Usage: $0 <DOMAIN> [-f|--force]"
    echo "    <DOMAIN> - the domain to query"
    echo "    -f, --force - force downloading all results without prompt, even if results are more than 1000"
    exit 1
fi

force_download=false
if [ "$#" -eq 2 ] && ([[ "$2" == "-f" ]] || [[ "$2" == "--force" ]]); then
    force_download=true
fi

# Define the API key and the base URL for the Netlas API
API_KEY="YOUR_API_KEY"
BASE_URL="https://app.netlas.io/api"

# Fetch organization name from WHOIS data
org_response=$(curl -s -H "X-API-Key: $API_KEY" \
        "$BASE_URL/whois_domains/?q=domain:$1")
organization=$(echo "$org_response" \
        | jq -r '.items[0].data.registrant.organization')

if [ "$organization" == "null" ] || [ -z "$organization" ]; then
    echo "No organization found for the domain."
    exit 1
fi

# Create query with escaped quotes for download endpoint and URL-encoded query
domains_query="registrant.organization:\\\""$organization"\\\""
domains_query_encoded=$(echo -n "registrant.organization:\""$organization"\"" \
        | jq -Rr @uri)

#Count domains with the same organization name
count_response=$(curl -s -H "X-API-Key: $API_KEY" \
        "$BASE_URL/whois_domains_count/?q=$domains_query_encoded")
count=$(echo "$count_response" | jq ".count")

# Check if there are any results and optionally ask for confirmation
if [ "$count" -eq 0 ]; then
    echo "No related domains found."
    exit 1
elif [ "$count" -gt 1000 ] && ! $force_download; then
    echo "Found $count results for organization: $organization."
    read -p "Do you really want to download all results? (Y/n): " user_input
    if [[ "$user_input" != "Y" && "$user_input" != "y" ]]; then
        echo "Download canceled."
        exit 0
    fi
fi

# Fetching domains registered to the same organization
curl -s -X POST "$BASE_URL/whois_domains/download/" \
        -H "Content-Type: application/json" \
        -H "X-API-Key: $API_KEY" \
        -d '{
            "q": "'"${domains_query}"'",
            "fields": ["domain"],
            "source_type": "include",
            "size": '"${count}"'
            }' | jq -r ".[].data.domain"
~ # bash related_domains-curl.sh microsoft.com 
Found 33304 results for organization: Microsoft Corporation.
Do you really want to download all results? (Y/n): Y
halo5.fans
session-verify-user.info
ndclient.net
indiaappfest.com
decloudapp.com
...
related_domains.py
import sys
import netlas
import json

# Check if a domain and optionally a force flag are provided
if len(sys.argv) < 2 or (len(sys.argv) == 3 and sys.argv[2] not in ['-f', '--force']):
    print("Usage: python related_domains.py <DOMAIN> [-f|--force]")
    print("\t<DOMAIN> - the domain to query")
    print("\t-f, --force - force downloading all results without prompt, even if results are more than 1000")
    sys.exit(1)

force_download = '-f' in sys.argv or '--force' in sys.argv

# Initialize the Netlas API assuming that API key is saved
api_key = netlas.helpers.get_api_key()
netlas_api = netlas.Netlas(api_key=api_key)

# Get registrant.organization by domain name
try:
    org_query = "domain:" + sys.argv[1]
    response = netlas_api.search(query=org_query, datatype='whois-domain')
    organization = response['items'][0]['data']['registrant']['organization']
except Exception as e:
    print("An error occurred:", str(e))
    sys.exit(2)

# Fetching domains registered to the same organization
try:
    # Get the count of domains
    domains_query = 'registrant.organization:"' + organization + '"'
    cnt_of_res = netlas_api.count(query=domains_query, datatype='whois-domain')

    # Check if there are any results and optionally ask for confirmation
    if cnt_of_res['count'] > 0:
        if cnt_of_res['count'] > 1000 and not force_download:
            print(f"Found {cnt_of_res['count']} results for organization: {organization}.")
            user_input = input("Do you really want to download all results? (Y/n): ")
            if user_input != 'Y':
                print("Download canceled.")
                sys.exit(0)

        # Downloading domains
        for resp in netlas_api.download(
            query=domains_query, 
            size=cnt_of_res['count'],
            datatype='whois-domain',
            fields='domain'
        ):
            # decode from binary stream
            response = json.loads(resp.decode('utf-8'))
            print(response['data']['domain'])
    else:
        print("No results for the given domain.")
except Exception as e:
    print("An error occurred:", str(e))
~ # python related_domains.py microsoft.com 
Found 33304 results for organization: Microsoft Corporation.
Do you really want to download all results? (Y/n): Y
halo5.fans
session-verify-user.info
ndclient.net
indiaappfest.com
decloudapp.com
...
Please note, data availability depends on your pricing plan

The Domain WHOIS Search tool is only available on paid pricing plans, and exceeding the allowed number of results may result in an error.

Exposed Ports History

This script uses the Netlas Search API to retrieve and display open ports for a specified IP address across indices available to the user, sorted in historical order.

ip_history-cli.sh
#!/bin/bash

# Check if an IP address is provided
if [ "$#" -lt 1 ]; then
    echo "Usage: bash ip_history.sh <IP_ADDRESS>"
    exit 1
fi

# Define the query
query="host:$1"

# Fetch the indices and sort by ID
indices=$(netlas indices --format json \
    | jq '.[] | select(.type | contains("responses")) | {id, name}' \
    | jq -s 'sort_by(.id)')

# Iterate through indices to fetch exposed ports data
echo "$indices" | jq -c '.[]' | while read -r idx; do
    index_id=$(echo "$idx" | jq -r '.id')
    index_name=$(echo "$idx" | jq -r '.name')

    # Get count of results
    cnt_of_res=$(netlas count "$query" --indices "$index_id" --format json \
        | jq '.count')

    # Check if there are any results and download them if there are
    if [ "$cnt_of_res" -gt 0 ]; then
        printf '%s\t' "Index #$index_id - $index_name"

        responses=$(netlas download "$query" \
            --include "port,protocol" --count "$cnt_of_res" \
            --indices "$index_id" | jq -c '.[]')

        exposed_ports=()

        while read -r resp; do
            port=$(echo "$resp" | jq -r '.port')
            protocol=$(echo "$resp" | jq -r '.protocol')

            # Check for uniqueness and add to array if not already present
            if [[ ! " ${exposed_ports[@]} " =~ " "${port}/${protocol}" " ]]; then
                exposed_ports+=("${port}/${protocol}")
            fi
        done <<< "$responses" # Pass responses as input to the while loop

        # Sort and output data
        sorted_ports=$(printf '%s\n' "${exposed_ports[@]}" | sort -t '/' -k1,1n -k2,2)
         echo $(tr '\n' ' ' <<< "$sorted_ports")
    fi

    # Wait a second to avoid throttling
    sleep 1
done
~ # bash ip_history-cli.sh 1.1.1.1
Index #83 - responses-2022-09-21: 53/dns 80/http 443/https
Index #84 - responses-2023-01-03: 53/dns 80/http 443/https 2095/http 8080/http
Index #86 - responses_*_2023-05-11: 53/dns 53/dns_tcp 80/http 443/https 2095/http 8080/http
Index #89 - responses_*_2023-09-22: 53/dns 53/dns_tcp 80/http 443/https 2095/http 8080/http
Index #103 - responses_*_2024-01-22:  53/dns 53/dns_tcp 80/http 443/https 2095/http 8080/http
Index #110 - responses_*_2024-04-25:  53/dns 53/dns_tcp 80/http 443/https 2095/http 2096/https 8080/http 8443/http 8443/https
ip_history-curl.sh
#!/bin/bash

# Check if an IP address is provided
if [ "$#" -lt 1 ]; then
    echo "Usage: bash ip_history.sh <IP_ADDRESS>"
    exit 1
fi

# Define the API key and the base URL for the Netlas API
API_KEY="YOUR_API_KEY"
BASE_URL="https://app.netlas.io/api"

# Define the query
query="host:$1"

# Fetch the indices and sort by ID
indices=$(curl -s "$BASE_URL/indices/" \
    | jq '.[] | select(.type | contains("responses")) | {id, name}' \
    | jq -s 'sort_by(.id)')

# Iterate through indices to fetch exposed ports data
echo "$indices" | jq -c '.[]' | while read -r idx; do
    index_id=$(echo "$idx" | jq -r '.id')
    index_name=$(echo "$idx" | jq -r '.name')

    # Get count of results
    cnt_of_res=$(curl -s -H "X-API-Key: $API_KEY" \
        "$BASE_URL/responses_count/?q=$query&indices=$index_id" | jq '.count')

    # Check if there are any results and download them if there are
    if [ "$cnt_of_res" -gt 0 ]; then
        printf '%s\t' "Index #$index_id - $index_name"

        responses=$(curl -s -X POST "$BASE_URL/responses/download/" \
            -H "X-API-Key: $API_KEY" \
            -H "Content-Type: application/json" \
            -d '{
                "q": "'"$query"'",
                "fields": ["port","protocol"],
                "source_type": "include",
                "size": '"${cnt_of_res}"',
                "indices": "'"$index_id"'"
                }')

        exposed_ports=()
        responses=$(echo "$responses" | jq -c '.[]')
        while read -r resp; do
            port=$(echo "$resp" | jq -r '.data.port')
            protocol=$(echo "$resp" | jq -r '.data.protocol')

            # Check for uniqueness and add to array if not already present
            if [[ ! " ${exposed_ports[@]} " =~ " "${port}/${protocol}" " ]]; then
                exposed_ports+=("${port}/${protocol}")
            fi
        done <<< "$responses" # Pass responses as input to the while loop

        # Sort and output data
        sorted_ports=$(printf '%s\n' "${exposed_ports[@]}" \
            | sort -t '/' -k1,1n -k2,2)
        echo $(tr '\n' ' ' <<< "$sorted_ports")
    fi

    # Wait a second to avoid throttling
    sleep 1
done
~ # bash ip_history-curl.sh 1.1.1.1
Index #83 - responses-2022-09-21: 53/dns 80/http 443/https
Index #84 - responses-2023-01-03: 53/dns 80/http 443/https 2095/http 8080/http
Index #86 - responses_*_2023-05-11: 53/dns 53/dns_tcp 80/http 443/https 2095/http 8080/http
Index #89 - responses_*_2023-09-22: 53/dns 53/dns_tcp 80/http 443/https 2095/http 8080/http
Index #103 - responses_*_2024-01-22:  53/dns 53/dns_tcp 80/http 443/https 2095/http 8080/http
Index #110 - responses_*_2024-04-25:  53/dns 53/dns_tcp 80/http 443/https 2095/http 2096/https 8080/http 8443/http 8443/https
ip_history.py
import sys
import time
import netlas
import json

# Check if an IP address is provided
if len(sys.argv) < 2:
    print("Usage: python ip_history.py <IP_ADDRESS>")
    sys.exit(1)

# Initialize the Netlas API assuming that API key is saved
api_key = netlas.helpers.get_api_key()
netlas_api = netlas.Netlas(api_key=api_key)

# Define the query
query = "host:" + sys.argv[1]

try:
    # Fetch the indices
    indices = netlas_api.indices()

    # Filter indices where the type contains 'responses' and sort by id
    filtered_indices = [index for index in indices if 'responses' in index['type']]
    sorted_indices = sorted(filtered_indices, key=lambda x: x['id'])

    # Iterating though indices to fetch exposed ports data
    for index in sorted_indices:
        cnt_of_res = netlas_api.count(query=query, indices=str(index['id']))

        # Check if there are any results and download them if there are
        if cnt_of_res['count'] > 0:
            exposed_ports = []
            print(f"Index #{index['id']} - {index['name']}:", end="\t")

            for resp in netlas_api.download(
                query=query, fields='port,protocol',
                size=cnt_of_res['count'], indices=str(index['id'])
                ):
                response = json.loads(resp.decode('utf-8'))

                # Fill a list with unique ports
                port = f"{response['data']['port']}/{response['data']['protocol']}"
                if port not in exposed_ports:
                    exposed_ports.append(port)

            # Sort and output data
            sorted_ports = sorted(
                exposed_ports, 
                key=lambda x: (int(x.split('/')[0]), x.split('/')[1])
                )
            print(' '.join(sorted_ports))

        # Wait a second to avoid throttling
        time.sleep(1)
except Exception as e:
    print("An error occurred:", str(e))
~ # python ip_history.py 1.1.1.1
Index #83 - responses-2022-09-21: 53/dns 80/http 443/https
Index #84 - responses-2023-01-03: 53/dns 80/http 443/https 2095/http 8080/http
Index #86 - responses_*_2023-05-11: 53/dns 53/dns_tcp 80/http 443/https 2095/http 8080/http
Index #89 - responses_*_2023-09-22: 53/dns 53/dns_tcp 80/http 443/https 2095/http 8080/http
Index #103 - responses_*_2024-01-22:  53/dns 53/dns_tcp 80/http 443/https 2095/http 8080/http
Index #110 - responses_*_2024-04-25:  53/dns 53/dns_tcp 80/http 443/https 2095/http 2096/https 8080/http 8443/http 8443/https

See Also

Explore more examples of using the Netlas API in these resources: