netintel

Network, DNS, TLS and email intelligence as a paid API. Per-call micropayments over x402 — no account, no API key, no minimum, no subscription.

from $0.002 / call USDC on Base zero npm dependencies runs at the Cloudflare edge failed requests are free

Endpoints & pricing

EndpointPriceWhat it does
GET /whoamiFREEEverything netintel can see about your connection: IP, geo, ASN, TLS version and cipher, HTTP protocol, Cloudflare colo, TCP RTT.
POST /dns$0.002Any record type over DNS-over-HTTPS, with TTLs, RCODE, the authoritative flag, DNSSEC validation status from the AD bit, and per-type timing.
POST /email$0.003A full deliverability audit of one address without sending anything to it.
POST /tls$0.004Certificate and HTTPS posture, graded A–F.
POST /ip$0.002An IPv4/IPv6 address, a CIDR block or a hostname in; network classification, reverse DNS, ASN and blocklist status out.
POST /propagation$0.005One record, up to eleven public resolvers, all queried at the same time.
GET /FREEThis page.
GET /openapi.jsonFREEOpenAPI 3.1 specification of every route.
GET /healthFREE{"ok":true}

Start here: GET /whoami is free, forever

It is the best possible demo of what an edge-hosted network API can see, it needs no payment and no client library, and it returns exactly the shape of data the paid endpoints return for hosts you name.

curl -s https://netintel.example/whoami
Response
{
  "ip": "203.0.113.9",
  "ipVersion": "IPv4",
  "geo": { "country": "GB", "region": "England", "city": "Slough",
           "timezone": "Europe/London", "latitude": 51.50949, "longitude": -0.59541,
           "continent": "EU", "postalCode": "SL1", "countryIsEU": false },
  "network": { "asn": 2856, "asOrganization": "BTBROADBAND", "colo": "LHR" },
  "tls": { "version": "TLSv1.3", "cipher": "AEAD-AES256-GCM-SHA384" },
  "http": { "protocol": "HTTP/2", "userAgent": "curl/8.14.1" },
  "performance": { "clientTcpRttMs": 7 }
}

Fields Cloudflare did not supply come back null — never guessed. Then point /ip or /tls at any host you like.

POST /dns

POST /dns DNS $0.002

Any record type over DNS-over-HTTPS, with TTLs, RCODE, the authoritative flag, DNSSEC validation status from the AD bit, and per-type timing. Every answer is cross-checked against a second independently-operated resolver and any disagreement is reported with an interpretation — a genuinely useful signal for stale caches, split-horizon DNS and interception.

Request
curl -s -X POST https://netintel.example/dns \
  -H 'content-type: application/json' \
  -d '{"name":"cloudflare.com","types":["A","AAAA","MX","CAA"],"dnssec":true}'
Response (abridged)
{
  "name": "cloudflare.com",
  "records": {
    "A": {
      "status": "NOERROR",
      "authenticatedData": true,
      "answers": [{ "type": "A", "ttl": 300, "data": "104.16.132.229" }],
      "ms": 41,
      "crossCheck": { "resolver": "google", "agrees": true, "ms": 38 }
    },
    "CAA": {
      "answers": [{ "type": "CAA", "data": "0 issue \"letsencrypt.org\"",
        "parsed": { "flags": 0, "tag": "issue", "value": "letsencrypt.org" } }]
    }
  },
  "dnssec": { "validation": "secure", "authenticatedData": true },
  "disagreements": [],
  "warnings": []
}

POST /email

POST /email Email $0.003

A full deliverability audit of one address without sending anything to it. Real RFC 5322 parsing, MX reachability, an SPF DNS-lookup count that actually expands every include, DMARC with organizational-domain fallback, DKIM selector probing with key sizes, and disposable/role/free-provider classification — ending in a 0–100 score whose every point is explained.

Request
curl -s -X POST https://netintel.example/email \
  -H 'content-type: application/json' \
  -d '{"email":"billing@paypal.com"}'
Response (abridged)
{
  "score": 91, "grade": "A", "verdict": "high",
  "syntax": { "valid": true, "domain": "paypal.com" },
  "mx": { "present": true, "count": 6, "allResolve": true,
          "primaryExchange": "mx1.paypal.com", "provider": "Proofpoint" },
  "spf": { "present": true, "allQualifier": "-", "allMeaning": "fail (hard, recommended)",
           "dnsLookups": { "totalLookups": 9, "limit": 10, "exceedsLimit": false, "exact": true } },
  "dmarc": { "present": true, "policy": "reject", "percent": 100,
             "spfAlignment": "strict", "strength": "strong" },
  "dkim": { "foundCount": 1, "found": [{ "selector": "default", "keySize": 2048 }] },
  "classification": { "disposable": false, "roleAccount": true, "freeProvider": false },
  "breakdown": [
    { "signal": "dmarc-reject", "delta": 2, "reason": "DMARC p=reject — the strongest setting." },
    { "signal": "role-account", "delta": -10, "reason": "\"billing\" is a role address…" }
  ]
}

POST /tls

POST /tls TLS $0.004

Certificate and HTTPS posture, graded A–F. netintel performs its own TLS 1.2 handshake over a raw TCP socket and parses the X.509 DER itself — which is why it can show you an expired or self-signed certificate that a normal HTTPS client refuses to connect to at all.

Request
curl -s -X POST https://netintel.example/tls \
  -H 'content-type: application/json' \
  -d '{"host":"expired.badssl.com"}'
Response (abridged)
{
  "grade": "F", "score": 33,
  "certificate": {
    "subject": { "commonName": "*.badssl.com" },
    "issuer": { "commonName": "COMODO RSA Domain Validation Secure Server CA" },
    "validity": { "notAfter": "2015-04-12T23:59:59.000Z",
                  "daysUntilExpiry": -4128, "expired": true },
    "serialNumber": "4E5BC1FE0D26F1E38A4B1E4F1F4B24F1",
    "signatureAlgorithm": { "name": "sha256WithRSAEncryption", "weak": false },
    "publicKey": { "algorithm": "RSA", "keySize": 2048 },
    "hostnameMatch": { "matches": true, "viaWildcard": true }
  },
  "certificateIssues": ["Certificate EXPIRED 4128 day(s) ago…"],
  "handshake": { "negotiatedVersion": "TLSv1.2",
                 "cipherSuite": "ECDHE-RSA-AES128-GCM-SHA256", "forwardSecrecy": true },
  "verification": { "trustPathVerified": false, "revocationChecked": false, "note": "…" }
}

POST /ip

POST /ip IP $0.002

An IPv4/IPv6 address, a CIDR block or a hostname in; network classification, reverse DNS, ASN and blocklist status out. Private addresses are welcome input and fully described — netintel just never opens a connection to anything you name here, so it cannot be turned into a network scanner.

Request
curl -s -X POST https://netintel.example/ip \
  -H 'content-type: application/json' \
  -d '{"ip":"8.8.8.8"}'
Response (abridged)
{
  "ip": "8.8.8.8", "version": "IPv4",
  "network": { "class": "public", "isPublic": true,
               "description": "globally routable public unicast" },
  "reverseDns": { "present": true, "primary": "dns.google" },
  "forwardConfirmedReverseDns": { "confirmed": true,
    "note": "Forward-confirmed: the PTR hostname resolves back to this address." },
  "asn": { "asn": 15169, "asName": "GOOGLE", "organization": "Google LLC, US",
           "prefix": "8.8.8.0/24", "countryCode": "US", "registry": "ARIN",
           "upstreamPeers": ["AS2914", "AS3257", "AS6461", "AS6939"] },
  "blocklists": { "checked": 6, "listedCount": 0, "clean": true,
    "unavailable": [{ "zone": "zen.spamhaus.org",
      "reason": "Spamhaus blocks queries arriving via public/open resolvers…" }] }
}

POST /propagation

POST /propagation DNS $0.005

One record, up to eleven public resolvers, all queried at the same time. Tells you each resolver's answer, TTL, latency and RCODE, groups them into distinct answer sets, and says whether the record has actually propagated — or whether one operator is filtering it.

Request
curl -s -X POST https://netintel.example/propagation \
  -H 'content-type: application/json' \
  -d '{"name":"example.com","type":"A"}'
Response (abridged)
{
  "name": "example.com", "type": "A",
  "consistent": true, "agreementRatio": 1,
  "resolversQueried": 8, "resolversAnswered": 8, "distinctAnswerSets": 1,
  "majorityAnswer": { "answers": ["104.20.23.154", "172.66.147.243"], "resolverCount": 8 },
  "resolvers": [
    { "id": "cloudflare", "operator": "Cloudflare", "ok": true, "ms": 38,
      "answers": ["104.20.23.154", "172.66.147.243"], "agreesWithMajority": true },
    { "id": "dnspod", "operator": "Tencent", "ok": true, "ms": 210,
      "answers": ["104.20.23.154", "172.66.147.243"], "agreesWithMajority": true }
  ],
  "interpretation": "All 8 responding resolvers returned identical data…"
}

How payment works

Every paid endpoint speaks x402 v2. There is nothing to sign up for.

  1. Call the endpoint with no credentials. You get HTTP 402 and a PAYMENT-REQUIRED header containing a base64 challenge: the amount, the asset (USDC), the network (eip155:8453, Base mainnet) and the recipient.
  2. Sign an EIP-3009 transfer authorisation for that exact amount and retry with an X-PAYMENT header.
  3. You get the result, plus a PAYMENT-RESPONSE header with the settlement receipt.

A client library does all of this for you:

import { wrapFetchWithPayment } from "@x402/fetch";
const pay = wrapFetchWithPayment(fetch, wallet);

const r = await pay("https://netintel.example/tls", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ host: "github.com" }),
});
console.log((await r.json()).certificate.validity.daysUntilExpiry);
You never pay for a failed request. Settlement is skipped on any response with status ≥ 400 — a dead resolver, an unreachable host, a refused target or an internal error all cost you nothing. Input is validated before the 402 challenge is issued, so malformed input is free too and reveals nothing about our infrastructure.

What netintel can and cannot see

Every external data source used here was tested from inside the Workers runtime before being shipped. Sources that don't work from a Worker are named in the response that would otherwise have used them, with the reason, instead of being quietly dropped or faked.

Confirmed working

✓ DoH · 11 resolversCloudflare, Google, Cisco, AdGuard, xTom, NextDNS, Control D, CleanBrowsing, Tencent, Alibaba Cloud
✓ Team Cymru IP→ASNorigin.asn.cymru.com and AS<n>.asn.cymru.com over DoH, plus peer ASNs
✓ Raw TLS handshakecloudflare:sockets reads full certificate chains, including expired and self-signed
✓ 6 DNS blocklistsbl.spamcop.net, b.barracudacentral.org, dnsbl-1.uceprotect.net, psbl.surriel.com, all.s5h.net, dnsbl.dronebl.org
✓ request.cf edge datageo, ASN, TLS version/cipher, colo, TCP RTT
✓ IDN → punycodenative IDNA in the platform URL parser

Confirmed not working from a Worker — and reported as such

✗ Quad9Quad9 enforces RFC 8484 §5.2 and rejects HTTP/1.1 with 505 HTTP Version Not Supported. The Workers fetch() runtime negotiates HTTP/1.1 to this origin and cannot be t…
✗ Comodo Secure DNSTLS connection fails from a Worker (fetch throws an opaque internal error). The endpoint appears defunct.
✗ Mullvad DNSConnection dropped from a Worker ('Network connection lost').
✗ dns0.euConnection dropped from a Worker ('Network connection lost').
✗ Hurricane ElectricRequires HTTP/2 (returns the same RFC 8484 §5.2 refusal as Quad9).
✗ Spamhaus ZENSpamhaus blocks queries arriving via public/open resolvers. Asking through Cloudflare or Google DoH returns the sentinel 127.255.255.254 with TXT 'Error: open resolv…
✗ Composite Blocking ListNow operated by Spamhaus and behind the same open-resolver refusal.
✗ SORBSSORBS was shut down in 2024; the zone returns NXDOMAIN for every query, including its own test entries.

Deliberately not claimed

Abuse guard

/tls is the only endpoint that makes an outbound connection to a host you name, and it refuses: private, loopback, link-local, CGNAT, reserved, documentation and multicast address space; cloud-metadata addresses including 169.254.169.254; any scheme other than http/https; internal-looking suffixes such as .internal, .local and .localhost; and every port that is not a TLS port.

Crucially, a hostname is resolved first and every address it resolves to is re-checked, which is what defeats DNS rebinding — localtest.me, 127.0.0.1.nip.io and friends all resolve to loopback and are refused. The handshake then connects to that pinned, validated address with the hostname sent only as SNI, so no DNS answer can change between the check and the connection. Redirects are re-validated at every hop. /ip will happily describe a private address but never connects to one.

Errors

Every error is a JSON object with a stable machine-readable code:

{ "error": { "code": "target_not_allowed",
             "message": "Target 169.254.169.254 is link-local (RFC 3927 link-local — includes cloud metadata 169.254.169.254); connections to non-public address space are refused.",
             "details": { "ip": "169.254.169.254", "class": "link-local", "isCloudMetadata": true } } }
StatusMeaningCharged?
400Malformed input — validated before the payment challenge.No
402Payment required; the challenge is in the PAYMENT-REQUIRED header.No
403Target refused by the abuse guard.No
404Host or domain does not exist (NXDOMAIN).No
413Request body too large.No
422Well-formed but unactionable — e.g. a hostname with no address records.No
500Our fault. Settlement is skipped.No
502Payment facilitator unreachable.No

Partial success is never silently hidden. Every response carries a warnings[] array, and any sub-lookup that failed appears there with its field set to null rather than to a misleading empty or negative result. /email and /ip additionally list every failure in failedLookups[]. A single dead resolver degrades one field; it never fails your request.