GET/api/v2/port

Port check API

Whether TCP ports on a host are open, closed or filtered, checked from Cloudflare's edge, with each open port's banner.

GEThttps://dnsrobot.netBase URL/api/v2Version/portEndpoint?host=&port=&ports=Parameters
200 OKRecorded 2026-10-25. Send it to run the request live.
{
  "query": {
    "host": "github.com",
    "ports": [
      22,
      80,
      443
    ]
  },
  "host": "github.com",
  "ip": "140.82.114.4",
  "addresses": [
    "140.82.114.4"
  ],
  "network": {
    "asn": 36459,
    "name": "GitHub",
    "prefix": "140.82.114.0/24",
    "country": "US"
  },
  "cloudflare": false,
  "checkedFrom": {
    "colo": "HYD"
  },
  "summary": {
    "open": 3,
    "closed": 0,
    "filtered": 0,
    "unchecked": 0
  },
  "ports": [
    {
      "port": 22,
      "service": "SSH",
      "transport": "tcp",
      "status": "open",
      "reason": "connected",
      "rttMs": 218,
      "banner": "SSH-2.0-35b973d6",
      "server": null,
      "httpStatus": null,
      "tlsVersion": null,
      "risk": "medium"
    },
    {
      "port": 80,
      "service": "HTTP",
      "transport": "tcp",
      "status": "open",
      "reason": "connected",
      "rttMs": 204,
      "banner": "HTTP 301 Moved Permanently",
      "server": null,
      "httpStatus": 301,
      "tlsVersion": null,
      "risk": null
    },
    {
      "port": 443,
      "service": "HTTPS",
      "transport": "both",
      "status": "open",
      "reason": "connected",
      "rttMs": 223,
      "banner": "TLS",
      "server": null,
      "httpStatus": null,
      "tlsVersion": null,
      "risk": null
    }
  ],
  "issues": [
    {
      "severity": "info",
      "code": "exposed_remote",
      "message": "SSH (22) is open to the internet. Remote logins are scanned and attacked around the clock. Allow only your own addresses, or reach it through a VPN."
    }
  ],
  "tookMs": 1114
}

How it works

Resolves the host once (every IPv4 address must be public), then opens a TCP connection to each port at its first IPv4 address from the Cloudflare data centre that took your request (checkedFrom.colo). An open web port is sent a HEAD (its Server header and status), a TLS port a ClientHello (the version it answers), and other services' first words are kept as banner.

open: the connection completed. closed: the address refused it after a real round trip. filtered: no answer within 4 seconds (a firewall drops it), never reported as closed. unchecked (with reason): the edge can't read it honestly: an address on Cloudflare's own network (cloudflare_network: Workers can't connect to Cloudflare's addresses, and its ports answer for Cloudflare, not the origin) or an answer indistinguishable from the runtime's own refusal (ambiguous). Open risky services (databases, remote desktops, Docker) come back in issues.

Port 25 can't be checked (Cloudflare blocks it outbound) and IPv6 targets can't be reached: both are a 422. A TCP check says nothing about UDP-only services (WireGuard, Minecraft Bedrock).

Query parameters

  • hoststringrequired
    A hostname or public IPv4 address; a URL or host:port is read as its host.
  • portintegeroptional
    One TCP port, 1 to 65535. Send port or ports.
  • portsstringoptional
    Up to 10 ports, comma-separated; a range like 8000-8003 counts each port. Every 5 ports cost one unit of the limit.

Response fields

200 with this JSON object, plus tookMs, and incomplete with warnings when a source failed. Fields marked optional appear only when they apply.

  • queryobjectalways
    What was looked up, normalised (lower case, no trailing dot, IDNs as Punycode).
    • hoststringalways
      The host, without scheme, path or port; an internationalised name in its xn-- form.
    • portsarray of integersalways
      The ports checked, sorted.
  • hoststringalways
    The host checked.
  • ipstring or nullalways
    The address every port was dialled at (the host's first IPv4 address); null when it has none.
  • addressesarray of stringsalways
    Every IPv4 address the name has.
  • networkobject or nullalways
    The network announcing the address (Team Cymru), or null.
    Show 4 fields
    • asnintegeralways
      AS number.
    • namestringalways
      The network's name.
    • prefixstring or nullalways
      The announced prefix.
    • countrystring or nullalways
      The block's registered country.
  • cloudflarebooleanalways
    Whether the address is on Cloudflare's network: its ports answer for Cloudflare's edge, and the edge can't dial them (unchecked).
  • checkedFromobjectalways
    Where the connections were made.
    • colostring or nullalways
      The Cloudflare data centre (IATA code), when known.
  • summaryobjectalways
    How many ports in each state.
    Show 4 fields
    • openintegeralways
      Open.
    • closedintegeralways
      Closed.
    • filteredintegeralways
      Filtered.
    • uncheckedintegeralways
      Unchecked.
  • portsarray of objectsalways
    One row per port, in the order of query.ports.
    Show 11 fields
    • portintegeralways
      The port.
    • servicestring or nullalways
      The service usually on it ("HTTPS", "MySQL"), or null for a port we don't name.
    • transportstring or nullalways
      How that service talks: tcp, udp or both. A TCP check can't see a UDP service.

      one of "tcp", "udp", "both", null

    • statusstringalways
      open, closed, filtered or unchecked.

      one of "open", "closed", "filtered", "unchecked"

    • reasonstringalways
      The evidence: connected, refused, timeout (no answer in 4 s), or why it wasn't checked: cloudflare_network, ambiguous, no_address.

      one of "connected", "refused", "timeout", "cloudflare_network", "ambiguous", "no_address"

    • rttMsinteger or nullalways
      The connection's round trip for open and closed, in milliseconds.
    • bannerstring or nullalways
      What an open service said first (printable ASCII, at most 160 characters): nginx · HTTP 301 Moved Permanently, TLS 1.3, SSH-2.0-OpenSSH_9.6.
    • serverstring or nullalways
      A web port's Server header.
    • httpStatusinteger or nullalways
      A web port's HTTP status to our HEAD /.
    • tlsVersionstring or nullalways
      A TLS port's negotiated version (TLS 1.3, TLS 1.2).
    • riskstring or nullalways
      How risky the service is when open to everyone: high (attacked around the clock), medium (restrict it), null.

      one of "high", "medium", null

  • issuesarray of objectsalways
    Problems found in the target's configuration, worst first. Empty when there are none.
    • severitystringalways
      error breaks something, warning risks it, info is worth knowing.

      one of "error", "warning", "info"

    • codestringalways
      Stable machine-readable code.
    • messagestringalways
      The problem and what to do about it, in plain English.

Errors

  • 422 target_not_allowed The host is (or resolves to) a private or reserved address, has only IPv6 addresses, or a requested port is 25.
  • 503 unavailable DNS didn't answer for the host.

Plus the errors every endpoint can return: 400 for a missing or unreadable parameter, 429 past the limit, 503 when every source failed.

Code samples

curl -G "https://dnsrobot.net/api/v2/port" \
  --data-urlencode "host=github.com" \
  --data-urlencode "ports=22,80,443"
const params = new URLSearchParams({ host: "github.com", ports: "22,80,443" });
const res = await fetch(`https://dnsrobot.net/api/v2/port?${params}`);
const data = await res.json();
if (!res.ok) throw new Error(data.error.message);
console.log(data.ports);
import requests

r = requests.get(
    "https://dnsrobot.net/api/v2/port",
    params={"host": "github.com", "ports": "22,80,443"},
    timeout=30,
)
data = r.json()
if not r.ok:
    raise RuntimeError(data["error"]["message"])
print(data["ports"])
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
)

func main() {
	q := url.Values{"host": {"github.com"}, "ports": {"22,80,443"}}
	res, err := http.Get("https://dnsrobot.net/api/v2/port?" + q.Encode())
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	var data map[string]any
	if err := json.NewDecoder(res.Body).Decode(&data); err != nil {
		panic(err)
	}
	if res.StatusCode != http.StatusOK {
		panic(data["error"])
	}
	fmt.Println(data["ports"])
}
<?php
$query = http_build_query(["host" => "github.com", "ports" => "22,80,443"]);
$context = stream_context_create(["http" => ["ignore_errors" => true, "timeout" => 30]]);
$data = json_decode(file_get_contents("https://dnsrobot.net/api/v2/port?" . $query, false, $context), true);
if (isset($data["error"])) {
    throw new RuntimeException($data["error"]["message"]);
}
print_r($data["ports"]);
require "net/http"
require "json"

uri = URI("https://dnsrobot.net/api/v2/port")
uri.query = URI.encode_www_form(host: "github.com", ports: "22,80,443")
data = JSON.parse(Net::HTTP.get(uri))
raise data["error"]["message"] if data["error"]
puts data["ports"]
// Cargo.toml: reqwest = { version = "0.13", features = ["blocking", "json"] }, serde_json = "1"
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let res = reqwest::blocking::get("https://dnsrobot.net/api/v2/port?host=github.com&ports=22%2C80%2C443")?;
    let ok = res.status().is_success();
    let data: serde_json::Value = res.json()?;
    if !ok {
        return Err(data["error"]["message"].to_string().into());
    }
    println!("{}", data["ports"]);
    Ok(())
}
using System.Text.Json;

using var http = new HttpClient();
var res = await http.GetAsync("https://dnsrobot.net/api/v2/port?host=github.com&ports=22%2C80%2C443");
var data = JsonDocument.Parse(await res.Content.ReadAsStringAsync()).RootElement;
if (!res.IsSuccessStatusCode)
    throw new Exception(data.GetProperty("error").GetProperty("message").GetString());
Console.WriteLine(data.GetProperty("ports"));

SSH, HTTP and HTTPS on github.com

GET /api/v2/port?host=github.com&ports=22%2C80%2C443
200 OKRecorded 2026-10-25
{
  "query": {
    "host": "github.com",
    "ports": [
      22,
      80,
      443
    ]
  },
  "host": "github.com",
  "ip": "140.82.114.4",
  "addresses": [
    "140.82.114.4"
  ],
  "network": {
    "asn": 36459,
    "name": "GitHub",
    "prefix": "140.82.114.0/24",
    "country": "US"
  },
  "cloudflare": false,
  "checkedFrom": {
    "colo": "HYD"
  },
  "summary": {
    "open": 3,
    "closed": 0,
    "filtered": 0,
    "unchecked": 0
  },
  "ports": [
    {
      "port": 22,
      "service": "SSH",
      "transport": "tcp",
      "status": "open",
      "reason": "connected",
      "rttMs": 218,
      "banner": "SSH-2.0-35b973d6",
      "server": null,
      "httpStatus": null,
      "tlsVersion": null,
      "risk": "medium"
    },
    {
      "port": 80,
      "service": "HTTP",
      "transport": "tcp",
      "status": "open",
      "reason": "connected",
      "rttMs": 204,
      "banner": "HTTP 301 Moved Permanently",
      "server": null,
      "httpStatus": 301,
      "tlsVersion": null,
      "risk": null
    },
    {
      "port": 443,
      "service": "HTTPS",
      "transport": "both",
      "status": "open",
      "reason": "connected",
      "rttMs": 223,
      "banner": "TLS",
      "server": null,
      "httpStatus": null,
      "tlsVersion": null,
      "risk": null
    }
  ],
  "issues": [
    {
      "severity": "info",
      "code": "exposed_remote",
      "message": "SSH (22) is open to the internet. Remote logins are scanned and attacked around the clock. Allow…"
    }
  ],
  "tookMs": 1114
}

Shortened for the page: long lists show their first items.

Open and closed ports on Nmap's test host

GET /api/v2/port?host=scanme.nmap.org&ports=22%2C80%2C3306
200 OKRecorded 2026-10-25
{
  "query": {
    "host": "scanme.nmap.org",
    "ports": [
      22,
      80,
      3306
    ]
  },
  "host": "scanme.nmap.org",
  "ip": "45.33.32.156",
  "addresses": [
    "45.33.32.156"
  ],
  "network": {
    "asn": 63949,
    "name": "Akamai Connected Cloud",
    "prefix": "45.33.32.0/19",
    "country": "US"
  },
  "cloudflare": false,
  "checkedFrom": {
    "colo": "HYD"
  },
  "summary": {
    "open": 2,
    "closed": 1,
    "filtered": 0,
    "unchecked": 0
  },
  "ports": [
    {
      "port": 22,
      "service": "SSH",
      "transport": "tcp",
      "status": "open",
      "reason": "connected",
      "rttMs": 223,
      "banner": null,
      "server": null,
      "httpStatus": null,
      "tlsVersion": null,
      "risk": "medium"
    },
    {
      "port": 80,
      "service": "HTTP",
      "transport": "tcp",
      "status": "open",
      "reason": "connected",
      "rttMs": 247,
      "banner": "Apache/2.4.7 (Ubuntu) · HTTP 200 OK",
      "server": "Apache/2.4.7 (Ubuntu)",
      "httpStatus": 200,
      "tlsVersion": null,
      "risk": null
    },
    {
      "port": 3306,
      "service": "MySQL",
      "transport": "tcp",
      "status": "closed",
      "reason": "refused",
      "rttMs": 232,
      "banner": null,
      "server": null,
      "httpStatus": null,
      "tlsVersion": null,
      "risk": "high"
    }
  ],
  "issues": [
    {
      "severity": "info",
      "code": "exposed_remote",
      "message": "SSH (22) is open to the internet. Remote logins are scanned and attacked around the clock. Allow…"
    }
  ],
  "tookMs": 1123
}

Shortened for the page: long lists show their first items.

An address on Cloudflare's network

GET /api/v2/port?host=1.1.1.1&port=443
200 OKRecorded 2026-10-25
{
  "query": {
    "host": "1.1.1.1",
    "ports": [
      443
    ]
  },
  "host": "1.1.1.1",
  "ip": "1.1.1.1",
  "addresses": [
    "1.1.1.1"
  ],
  "network": {
    "asn": 13335,
    "name": "Cloudflare",
    "prefix": "1.1.1.0/24",
    "country": "AU"
  },
  "cloudflare": true,
  "checkedFrom": {
    "colo": "HYD"
  },
  "summary": {
    "open": 0,
    "closed": 0,
    "filtered": 0,
    "unchecked": 1
  },
  "ports": [
    {
      "port": 443,
      "service": "HTTPS",
      "transport": "both",
      "status": "unchecked",
      "reason": "cloudflare_network",
      "rttMs": null,
      "banner": null,
      "server": null,
      "httpStatus": null,
      "tlsVersion": null,
      "risk": null
    }
  ],
  "issues": [
    {
      "severity": "info",
      "code": "cloudflare_network",
      "message": "1.1.1.1 is on Cloudflare's network: its ports answer for Cloudflare's edge, not the server behin…"
    }
  ],
  "tookMs": 84
}

Shortened for the page: long lists show their first items.