API & query reference
The tttx API is free — unlike Shodan. Sign up for a key (1,000 requests/day). The same query language powers the site search box and the API.
Authentication
Send your key in the X-API-Key header (recommended) or as ?key=.
Only the /api/v1/* endpoints require a key.
curl -H "X-API-Key: YOUR_KEY" "https://tttx.net/api/v1/host?ip=154.59.103.7" curl "https://tttx.net/api/v1/search?q=product:nginx&key=YOUR_KEY"
Endpoints
| Endpoint | Purpose |
|---|---|
GET /api/v1/host?ip= | All services on one IPv4 host. |
GET /api/v1/search?q=&page= | Hosts matching a query (20/page, collapsed by host). |
GET /api/hitlist?q= Pro | Risk-ranked hosts (KEV + public exploit + CVSS + exposure) with a reason per host. Analyst tool — Pro key or admin session. |
GET /api/graph?ip= Pro | Cert/SAN/favicon/JARM pivot graph — a seed host's shared-infrastructure siblings as nodes+edges. Pro key or admin session. |
GET /api/fingerprints?by= Pro | Corpus-wide fleets clustered by jarm/server/favicon/issuer/product, with per-cluster CVE counts. Pro key or admin session. |
GET /api/changes?type=&since= Pro | Posture-change feed from re-scan diffs: new CVEs (KEV-flagged), exposures, defacements, cert swaps, version changes. Pro key or admin session. |
GET /api/outliers?q= Pro | Anomaly detectors: data stores on odd ports, cleartext credential pages, untrusted TLS on high-trust sites, shells on web ports. Pro key or admin session. |
GET /api/opendirs?q= Pro | Public "Index of /" listings ranked by sensitive filenames. Pair with opendir_triage. Pro key or admin session. |
GET /api/opendir_triage?ip=&port= Pro | Live-fetch a listing + AI read for exposed secrets/backups/source. Pro key or admin session. |
GET /api/fleet_narrative?by=&key= Pro | AI intel brief for an infrastructure cluster (scale, stack, geography, CVE/KEV posture). Pro key or admin session. |
GET /api/visual_id?ip=&port= Pro | Vision model names the product/vendor from a screenshot (stored as product_guess). List mode without ip=. Pro key or admin session. |
POST /api/analyst Pro | Agentic analyst: plans + runs tttx queries to answer a natural-language question (async; poll GET ?job=). Pro key or admin session. |
GET /api/similar?ip= | Hosts whose rendered page looks like the seed host's (visual kNN). See Vector search. |
GET /api/semantic?q= | Hosts whose page means the same as a free-text phrase (semantic kNN). See Vector search. |
GET /api/stats | Corpus totals + top ports/products (no key). |
GET /api/ownership?q=&asn= | IPv4 ownership by ASN + subnets (no key). |
GET /api/v1/ai/host?ip= Pro | AI risk assessment + mitigation for a host. |
POST /api/v1/ai/query Pro | Natural language → a tttx query string (JSON {"text":…}). |
POST /api/v1/ai/ask Pro | Analyst assistant (JSON {"prompt":…, "model":"quick|reason"}). |
Endpoints tagged Pro require a Pro key — "AI testing & mitigation". They call our local models, so a response can take a few seconds to a minute.
URL-encode spaces in q as + or %20
(e.g. q=product:nginx+port:443).
Query filters
A query is space-separated tokens. key:value tokens filter; bare words are full-text
(matched against banners, page titles, product and AS names). An IP/CIDR bare token filters by address.
| Filter | Examples | Matches |
|---|---|---|
| (bare text) | nginx, login | Full-text across banners, titles, product & AS name. |
ip: | ip:1.1.1.1, ip:1.0.0.0/24, ip:102.0 | Exact IP, CIDR, or dotted prefix. |
port: | port:22, port:80,443, port:8000-8100 | Host has the port open. Comma = any (OR); a-b = range. |
ports_all: | ports_all:22,443 | Host has all listed ports open (AND). |
product: | product:nginx, product:OpenSSH | Identified service product (exact). |
country: | country:US | ISO-2 country (RIR-authoritative). |
asn: | asn:7018, asn:AS7018 | Autonomous system number. |
org: | org:Comcast | AS organization name (text match). |
owner: | owner:Cogent | RIR registrant (substring, case-insensitive). |
hostname: | hostname:example.com | TLS cert CN/SAN & page text (fuzzy match). |
san: | san:vpn.example.com | Exact TLS cert Subject Alternative Name. Facetable — the search response's facets include a san list of the most common SANs seen. |
server: | server:"nginx/1.24.0 (Ubuntu)" | Exact HTTP Server header (quote values with spaces). |
issuer: | issuer:Sectigo, issuer:"C=US, O=Let's Encrypt, CN=R3" | TLS cert issuer — case-insensitive substring, or exact for a full DN. |
cert: | cert:34ed5c7b… | Exact certificate by SHA-256 fingerprint (all hosts serving the identical cert). |
jarm: | jarm:2ad2ad0002ad2ad… | Active JARM TLS-stack fingerprint (clusters same-software/appliance servers). |
favicon: | favicon:999357577 | Shodan-style MMH3 favicon hash (same web app/brand across hosts). |
status: | status:200, status:401 | HTTP response status code. |
has_ssl: | has_ssl:true | TLS enabled on the service. |
expired: | expired:true | Expired TLS certificate. |
self_signed: | self_signed:true | Self-signed certificate. |
cve: | cve:CVE-2021-41773 | A specific CVE is present. |
has_vuln: | has_vuln:true | Has a likely-real CVE (false = none). Excludes backport-suspect findings — version-matches on a distro-managed package (OpenSSH, Apache, …) on a backporting distro (Ubuntu/Debian/RHEL/…), which the distro has very likely patched. Still findable via exact cve:. |
cvss: | cvss:>=9, cvss:>7.5, cvss:9, cvss:7-9 | Max CVSS of any likely-real CVE (backport-suspect excluded, as with has_vuln:). Operators > >= < <=, or a band a-b. |
os: | os:ubuntu, os:debian, os:centos | OS/distro inferred from the SSH banner package suffix or HTTP Server header. |
backport: | backport:confirmed-vulnerable, backport:confirmed-patched, backport:heuristic | Filter by backport confidence (OpenSSH is tracker-confirmed against Ubuntu/Debian fixed versions; others are heuristic). |
latency: | latency:<20, latency:>=200, latency:50 | TCP-handshake RTT in ms. Operators > >= < <= (bare number = at most). |
tag: | tag:control-panel, tag:login, tag:wordpress | Derived/heuristic tag — protocol, stack, exposure, or page-type category (see the tag list below). |
label: | label:"login page", label:"admin dashboard / control panel" | Page type from the screenshot/OCR classifier (quote multi-word values). See the label list below. |
app: / plugin: | app:WordPress, plugin:woocommerce, app:wp-plugin:elementor | Stacked app / CMS / WordPress plugin (plugin: is shorthand for app:wp-plugin:). |
app:…@version | app:WordPress@6.8.8 (exact), app:WordPress@6.8.* (prefix), app:WordPress@<6 / @>=5 / @<=6.8 (compare), app:WordPress@5-6 (band), plugin:woocommerce@8.5 | Pin a stacked app to a version. Comparisons (< <= > >=) and bands (a-b, half-open) sort numerically. Precise: product + version are bound to the same app (nested), so a plugin's version can't leak into a core-version filter. |
exposure: | exposure:open-redis, exposure:weak-rsa-key | A specific no-auth / weak-crypto exposure. |
no_auth: | no_auth:true | Any no-auth / anonymous exposure (Redis, Mongo…). |
Tags & page-type labels
Every service carries a tags array (fast, keyword-exact — use with tag:)
and, if a screenshot was captured, a page-type label from the vision/OCR classifier
(use with label:"…", quoting multi-word values). Hunt for interesting surfaces like this:
| Kind | Values | Example |
|---|---|---|
| Protocol / transport | web, tls, ssh, ftp, smtp, imap, pop3, dns | tag:web tag:tls |
| Certificate | self-signed, expired-cert | tag:self-signed |
| Risk | vuln, no-auth, weak-key, tarpit | tag:no-auth |
| Stack | wordpress, wordpress-plugin, wordpress-theme, + per-product tags | tag:wordpress |
| Page type (from OCR/vision) | login, control-panel, webapp, ecommerce, blog-news, webmail, vpn, dir-listing, api-endpoint, iot-camera, defaced | tag:vpn |
label: values | "login page", "admin dashboard / control panel", "web application / SaaS", "online store / e-commerce", "blog / news / media", "corporate / business site", "webmail", "VPN / remote-access portal", "directory listing", "API / JSON endpoint", "camera / IoT device UI", "default/placeholder page", "error page", "other / uncategorized" | label:"web application / SaaS" |
The short page-type tag:s are set from the OCR text during an OCR pass, so they cover
image/JS-rendered pages the DOM classifier misses. tag:login and label:"login page"
overlap; the tag is quicker to type, the label is exact.
Combining filters & gotchas
Tokens combine with AND at the service level. Because each open port is its own record, filters that describe a single service combine naturally:
product:nginx port:443 country:US has_vuln:true
But two port: filters
describe two different services, so port:22 port:443 matches nothing
(no single record is both). To require a host to have several ports open, use ports_all::
# WRONG — matches nothing: port:22 port:443 # RIGHT — hosts with BOTH 22 and 443 open: ports_all:22,443
Vector search (visual & semantic)
The keyword filters above match exact values. Vector search matches by similarity instead — "show me pages that look like this" and "find pages that mean this", even when they share no keywords. It's backed by two embeddings we compute per host from its screenshot:
| Vector | What it encodes | Model / dims |
|---|---|---|
screenshot_vec | The visual appearance of the rendered page (layout, colours, logos). | CLIP ViT-B-32 · 512-d |
text_vec | The meaning of the page's title + on-page (OCR) text + service banner. | nomic-embed-text · 768-d |
Both are indexed as OpenSearch knn_vector fields (HNSW, cosine). Nearest-neighbour
lookups are ranked by cosine similarity, returned per result as similarity (1.0 = identical).
Visual similarity — /api/similar
Give it a seed ip; it returns the hosts whose screenshots are closest to that
host's. No text needed — this finds the same login portal, appliance UI, or phishing-kit template deployed
across many IPs, including ones with no shared banner or certificate. In the web UI, open any host and click
“Similar pages”.
| Param | Default | Meaning |
|---|---|---|
ip (required) | — | Seed IPv4. Its stored screenshot_vec is the query vector (no GPU call). |
port | any | Pick a specific service's screenshot when the host has several. |
field | screenshot_vec | screenshot_vec (visual) or text_vec (by meaning of the seed's text). |
q | — | Optional structured filter to narrow the neighbour pool (same grammar as search). |
size | 24 | Max hosts to return (1–100). |
# Every host whose page looks like this one curl "https://tttx.net/api/similar?ip=203.0.113.10" # …but only German hosts on 443 curl "https://tttx.net/api/similar?ip=203.0.113.10&q=country:DE+port:443"
Semantic search — /api/semantic
Give it a natural-language q; we embed your phrase with the same model that
produced text_vec, then return the hosts whose page text is closest in meaning. So
database admin control panel surfaces phpMyAdmin, Adminer and Odoo logins even when the page
never uses those words. In the web UI, prefix the search box with ~:
~wordpress login page ~industrial control system HMI ~parked domain for sale
| Param | Default | Meaning |
|---|---|---|
q (required) | — | Free-text phrase describing the page you're after. |
and | — | Optional structured filter applied on top (same grammar as search), e.g. and=country:DE. |
size | 24 | Max hosts to return (1–100). |
# Pages that mean "remote desktop gateway", German only curl "https://tttx.net/api/semantic?q=remote+desktop+gateway&and=country:DE"
Coverage. A host is only returned once its
screenshot has been vectorised (an ongoing backfill over ~42k thumbnails). Hosts without a screenshot — or
not yet processed — won't appear. Visual results need screenshot_vec; semantic results need
text_vec, which exists only when the page had a title/OCR text/banner to embed.
Similarity vs. filters. kNN returns a fixed
top-k ranked by closeness, not a filtered set — so there are no facets or deep pagination, and a
low-scoring tail is expected. Combine with structured filters (q/and) to keep the
neighbour pool tight.
Worked examples
# One host, everything we know about it curl -H "X-API-Key: KEY" "https://tttx.net/api/v1/host?ip=154.59.103.7" # nginx on 443 in the United States curl -H "X-API-Key: KEY" "https://tttx.net/api/v1/search?q=product:nginx+port:443+country:US" # Hosts exposing SSH and RDP (jump boxes) curl -H "X-API-Key: KEY" "https://tttx.net/api/v1/search?q=ports_all:22,3389" # Anything on the common web ports OR a high range curl -H "X-API-Key: KEY" "https://tttx.net/api/v1/search?q=port:80,443,8080,8000-8100" # Critical vulnerabilities (CVSS >= 9) curl -H "X-API-Key: KEY" "https://tttx.net/api/v1/search?q=has_vuln:true+cvss:>=9" # Exposed control-panel / admin login pages (no auth in front of them) curl -H "X-API-Key: KEY" "https://tttx.net/api/v1/search?q=tag:control-panel" curl -H "X-API-Key: KEY" "https://tttx.net/api/v1/search?q=label:%22login+page%22" # Internet-facing cameras / IoT device UIs curl -H "X-API-Key: KEY" "https://tttx.net/api/v1/search?q=tag:iot-camera" # Open directory listings on TLS in the US curl -H "X-API-Key: KEY" "https://tttx.net/api/v1/search?q=tag:dir-listing+has_ssl:true+country:US" # Unauthenticated databases exposed to the internet curl -H "X-API-Key: KEY" "https://tttx.net/api/v1/search?q=no_auth:true" # Expired certs on one operator's network curl -H "X-API-Key: KEY" "https://tttx.net/api/v1/search?q=asn:7018+expired:true" # Page 2 of a result set curl -H "X-API-Key: KEY" "https://tttx.net/api/v1/search?q=product:apache&page=1"
Limits & errors
- Quota: 1,000 requests/day per key (resets at UTC midnight).
401— missing/invalid/revoked key.429— daily quota exceeded (response includesdaily_quota).400— bad parameter (e.g. malformedip).ports_all:is resolved via an aggregation and capped at 10,000 matching hosts.- Opted-out ranges are excluded from all public results.
Machine-readable spec
An OpenAPI 3.0 document describes every endpoint, parameter, and response schema.
It's served with CORS, so you can point Swagger UI, Redoc, Postman, or a client generator straight at it.
The query filters are also enumerated under the q parameter's x-tttx-filters extension.
# Fetch the spec curl "https://tttx.net/api/openapi.json" # Generate a typed client (example) openapi-generator-cli generate -i https://tttx.net/api/openapi.json -g python -o ./tttx-client
Questions or abuse reports: abuse@tttx.net.