Skip to content

Shodan API

Member Freelancer Small Business Corporate Enterprise

The Shodan platform offers several APIs that are aimed at different use cases but in this article we will be discussing the main Shodan API. It provides programmatic access to the information that Shodan collects. All of the websites, including the main Shodan website, are powered by the main API. Everything that can be done via the website can be accomplished from your own code.

The main Shodan API is divided into 2 parts: REST API and Streaming API. The REST API provides methods to search Shodan, look up hosts, get summary information on queries (aka facet analysis), manage monitored assets and a variety of utility methods to make developing easier. The Streaming API provides a raw, real-time feed of the data that Shodan is currently collecting. There are several feeds that can be subscribed to, but the data can't be searched or otherwise interacted with; it's a live feed of data meant for either large-scale consumption of Shodan's information (enterprise-only) or storing data on monitored assets in a data lake.

Usage Limits

There are 3 types of credits that vary depending on your API plan:

  • query credits: They're used when searching (/shodan/host/search) or looking up domains (/dns/domain/{domain}). 1 query credit is deducted per 100 pages of search results or per page of domain information. IP lookups don't consume query credits.
  • scan credits: The on-demand scanning API uses scan credits to limit the number of hosts that you can request Shodan to scan every month. For every host that you request a scan of Shodan deducts 1 scan credit.
  • alert credits: They specify the number of IPs that can be monitored via Shodan Monitor. 1 alert credit lets you monitor 1 IP address.

Getting Started

All the examples will be provided in Python and assume you have access to the command-line, though there are Shodan libraries/ clients available in other languages as well.

To install the Shodan library for Python run the following command:

Terminal window
pip install shodan

If you already have it installed and want to upgrade to the latest version:

Terminal window
pip install -U shodan

Initialization

The first thing that always has to be done is initializing the Shodan API object:

import shodan
api = shodan.Shodan('YOUR API KEY')

Where YOUR API KEY is the API key for you account which you can obtain from:

https://account.shodan.io

Now that we have our API object all good to go, we're ready to perform a search:

# Wrap the request in a try/ except block to catch errors
try:
# Search Shodan
results = api.search('apache')
# Show the results
print(f"Results found: {results['total']}")
for banner in results['matches']:
print(f"IP: {banner['ip_str']}")
print(banner['data'])
print()
except shodan.APIError as e:
print(f"Error: {e}")

Stepping through the code, we first call the Shodan.search() method on the api object which returns a dictionary of result information. We then print how many results were found in total, and finally loop through the returned matches and print their IP and banner. Each page of search results contains up to 100 results.

There's a lot more information that gets returned by the function. See below for a shortened example dictionary that Shodan.search returns:

{
'total': 8669969,
'matches': [
{
'data': 'HTTP/1.0 200 OK\r\nDate: Mon, 08 Nov 2010 05:09:59 GMT\r\nSer...',
'hostnames': ['pl4t1n.de'],
'ip': 3579573318,
'ip_str': '89.110.147.239',
'os': 'FreeBSD 4.4',
'port': 80,
'timestamp': '2014-01-15T05:49:56.283713'
},
...
]
}

See the Datapedia for a complete list of properties that the banner may contain.

The above script only outputs the results from the 1st page of results. To get the 2nd page of results or more simply use the page parameter when doing the search request:

results = api.search('apache', page=2)

Or if you want to simply loop over all possible results there's a method to make your life easier called search_cursor()

for banner in api.search_cursor('apache'):
print(banner['ip_str']) # Print out the IP address for each banner

IP Lookup

To see what Shodan has available on a specific IP we can use the Shodan.host() function:

# Lookup the host
host = api.host('217.140.75.46')
# Print general info
print(f"""
IP: {host['ip_str']}
Organization: {host.get('org', 'n/a')}
Operating System: {host.get('os', 'n/a')}
""")
# Print all banners
for banner in host['data']:
print(f"""
Port: {banner['port']}
Banner: {banner['data']}
""")

By default, Shodan only returns information on the host that was recently collected. If you would like to get a full history of an IP address, include the history parameter. For example:

host = api.host('217.140.75.46', history=True)

The above would return historical data which includes banners that are up to 90 days old.

Scanning

Shodan crawls the Internet at least once a week and monitored assets at least daily. If you want to request Shodan to scan a network sooner then you can do so using the on-demand scanning capabilities of the API.

Unlike scanning via a tool such as Nmap, the scanning with Shodan is done asynchronously. This means that after you submit a request to Shodan you don't get back the results immediately. It is up to the developer to decide how the results of the scan should be gathered: by looking up the IP information, searching Shodan or subscribing to the real-time stream. The Shodan command-line interface creates a temporary network alert after a scan was initiated and then waits for results to come through the real-time stream.

scan = api.scan('198.20.69.0/24')

It's also possible to submit a list of networks at once by providing a list of addresses in CIDR notation:

scan = api.scan(['198.20.49.30', '198.20.74.0/24'])

After submitting a scan request the API will return the following information:

{
'id': 'R2XRT5HH6X67PFAB',
'count': 1,
'credits_left': 5119
}

The object provides a unique id that you can use for tracking purposes, the total count of IPs that were submitted for scanning and finally how many scan credits are left (credits_left).

Domain Lookup

Here is a short example on how to fetch DNS information for a domain:

# Grab all known subdomains for "shodan.io"
info = api.dns.domain_info('shodan.io')
print(info)

An abbreviated, sample JSON response is below:

{
"domain": "shodan.io",
"tags": [
"dmarc",
"google-verified",
"ipv6",
"microsoft-office-365",
"spf",
...
],
"subdomains": [
"*",
"2000",
"_dmarc",
"a",
"aaaa",
"account",
"api",
"atlantic.census",
"bacon.scanf",
"board.census",
"bone.scan6",
"book",
"border.census",
"burger.census",
"burger.scan6",
"butter.scanf",
"careers",
"census",
"census1",
"census10",
"census11",
"census12",
"census2",
"census3",
...
],
"data": [
{
"subdomain": "",
"type": "MX",
"value": "alt1.aspmx.l.google.com",
"last_seen": "2025-08-15T05:06:34.735000"
},
{
"subdomain": "",
"type": "MX",
"value": "alt2.aspmx.l.google.com",
"last_seen": "2025-08-15T05:06:34.735000"
},
{
"subdomain": "",
"type": "MX",
"value": "aspmx.l.google.com",
"last_seen": "2025-08-15T05:06:34.735000"
},
{
"subdomain": "",
"type": "NS",
"value": "ed.ns.cloudflare.com",
"last_seen": "2025-08-15T05:06:48.097000"
},
{
"subdomain": "",
"type": "NS",
"value": "lady.ns.cloudflare.com",
"last_seen": "2025-08-15T05:06:48.097000"
},
{
"subdomain": "",
"type": "SOA",
"value": "ed.ns.cloudflare.com",
"last_seen": "2025-08-15T05:10:05.460000"
},
{
"subdomain": "",
"type": "TXT",
"value": "MS=87251D03D1B817A70CC370D9FF17FFB7325A0110",
"last_seen": "2025-08-15T05:01:33.323000"
},
{
"subdomain": "",
"type": "TXT",
"value": "google-site-verification=BzXlMuKdXSAiX3v-VUo-M_ZZ1VPW3cwptvaIOWWGQ7c",
"last_seen": "2025-08-15T05:01:33.323000"
},
{
"subdomain": "",
"type": "TXT",
"value": "v=spf1 ip4:216.117.2.180 ip4:69.72.37.146 include:_spf.google.com -all",
"last_seen": "2025-08-15T05:01:33.323000"
},
{
"subdomain": "*",
"type": "A",
"value": "216.117.2.180",
"last_seen": "2025-08-15T05:12:13.340000"
},
{
"subdomain": "2000",
"type": "A",
"value": "104.18.12.238",
"last_seen": "2025-08-15T20:16:57.831000"
},
...
],
"more": false
}

Note the more property in the response. If it's set to true then more information is available and you can fetch the additional records by requesting the 2nd page.

Real-Time Stream

Enterprise

The Streaming API is an HTTP-based service that returns a real-time stream of data collected by Shodan. It doesn't provide any search or lookup capabilities.

For example, here is a script that outputs a stream of banners from devices that are vulnerable to FREAK (CVE-2015-0204):

def has_vuln(banner, vuln):
if 'vulns' in banner and vuln in banner['vulns']:
return True
return False
for banner in api.stream.banners():
if has_vuln(banner, 'CVE-2015-0204'):
print(banner)

To save space and bandwidth many properties in the banner are optional. To make working with optional properties easier it is best to wrap access to properties in a function. In the above example, the has_vuln() method checks whether the service is vulnerable for the provided CVE.