API Reference
READ-ONLY THREAT INTELLIGENCE NO API KEY REQUIRED
Everything the frontend shows is also available as plain JSON over HTTP.
Look up a specific attacker IP, hunt an IOC in the event store, or pull the same aggregates
that power the live map. All read endpoints are public — no account, no key. Responses are
JSON and always served with Cache-Control: no-store, so you never get stale data.
https://versec.nl/api
TRY IT — LOOK UP AN IP CURL BUILDER
Enter any IPv4 or IPv6 address and get the exact command to query its full threat-intelligence report — verdict, behaviour scoring, credentials tried, commands run, dropped files and timeline.
/api/ip/{ip}
The core lookup. Returns everything the honeypot sensors know about one source IP:
a behavioural verdict with a bot↔human score (0–100, 0 = clear bot/spray), confidence,
human-readable reasons, first/last seen, session and login statistics, the top tried
usernames, passwords and commands, dropped files, exploit attempts and a timeline of the most
recent events. If the IP is itself a known malware distribution host, its download-server record
is included as well. Answers {"known": false} for IPs that never touched the sensors
and 400 for a malformed address.
curl -s https://versec.nl/api/ip/132.148.73.100 | jq
{
"ip": "132.148.73.100",
"country": "US",
"known": true,
"first_seen": "2026-07-30T04:12:51",
"last_seen": "2026-08-19T11:03:22",
"n_events": 1240,
"verdict": "bot", // bot | likely_bot | unclear | likely_human | human
"score": 4, // 0–100 · 0 = clear bot / spray
"confidence": "high",
"reasons": ["same command sequence replayed across sessions", "…"],
"n_sessions": 96, "n_failed": 870, "n_success": 2, "n_cmds": 41, "n_downloads": 3,
"top_users": [{"value": "root", "count": 512}, "…"],
"top_passwords": [{"value": "123456", "count": 88}, "…"],
"top_commands": [{"value": "uname -a", "count": 12}, "…"],
"protocols": [{"value": "ssh", "count": 900}, "…"],
"dst_ports": [{"value": 22, "count": 900}, "…"],
"downloads": [{"ts": "…", "url": "http://…/b.sh", "shasum": "…", "size": 2210}],
"exploits": [{"ts": "…", "url": "…", "ok": false}],
"timeline": [{"ts": "…", "kind": "command", "text": "uname -a", "session": "…"}]
}/api/search?q={term}
Free-text search across the whole event store — matches source IPs, usernames,
passwords, messages and command input. Use it to hunt an IOC: a hash, a URL, a credential pair,
a command line. q is capped at 200 characters; you get up to 100 newest matches plus
the deduplicated list of involved IPs (hand those to /api/ip/{ip} for the full report).
curl -s 'https://versec.nl/api/search?q=Mirai' | jq
{
"query": "Mirai",
"hits": 12,
"src_ips": ["45.155.x.x", "…"],
"results": [
{
"ts": "2026-08-18T22:14:03",
"eventid": "cowrie.command.input",
"kind": "command", // command | login | download | exploit | session
"detail": "wget http://…/mirai.arm7",
"src_ip": "45.155.x.x", "src_port": 51234,
"protocol": "ssh", "sensor": "mowrie", "session": "…"
}
]
}/api/overview
Global platform counters — the same numbers shown on the landing page. Good for a quick sanity check that data is flowing.
curl -s https://versec.nl/api/overview | jq
{
"events_total": 1823401,
"src_ips": 90211,
"events_24h": 15320,
"src_ips_24h": 640,
"attacks": 455002,
"download_servers": 137,
"last_event": "2026-08-19T18:41:07"
}/api/feed?limit={n}
The newest core attack events (logins, commands, downloads, exploits) from the last 30 days, newest first — the stream behind the live feed panel. Each item carries its country code, so you can map or filter client-side.
curl -s 'https://versec.nl/api/feed?limit=10' | jq
{
"items": [
{
"ts": "2026-08-19T18:40:55",
"eventid": "cowrie.login.failed",
"kind": "login", "ok": false,
"detail": "root / 123456",
"src_ip": "203.0.113.7", "src_port": 40012, "dst_port": 22,
"protocol": "ssh", "country": "CN", "session": "…"
}
]
}/api/feed/geo
Attack origins aggregated per country over the last 30 days, bucketed by recency:
active (seen in the last 15 minutes), last24 and last30.
This is the dataset behind the world map — one call gives you a complete picture.
curl -s https://versec.nl/api/feed/geo | jq
{
"updated": "2026-08-19T18:41:10+00:00",
"active_window_min": 15,
"countries": [
{"cc": "CN", "events": 15230, "ips": 412, "active": 7, "last24": 55, "last30": 350,
"latest": "2026-08-19T18:39:01+00:00"},
{"cc": "US", "events": 9811, "ips": 301, "active": 2, "last24": 40, "last30": 260, "latest": "…"}
]
}/api/download-servers?limit={n}
The distribution infrastructure behind the attacks: hosts that dropped malware is downloaded from inside the honeypot, deduplicated per host and sorted by newest activity.
curl -s 'https://versec.nl/api/download-servers?limit=20' | jq
{
"count": 137,
"servers": [
{
"host": "94.156.x.x", "kind": "ip",
"downloads": 64,
"urls": ["http://94.156.x.x/b.sh", "…"],
"ports": [80], "src_ips": ["45.155.x.x", "…"], "sensors": ["mowrie"],
"first_ts": "2026-06-02T09:11:00", "last_ts": "2026-08-19T07:52:13"
}
]
}/api/download-server/{host}
Detail record for one distribution host — accepts an IP address or a domain.
Lists the URLs it served, the ports contacted, which attacker IPs pulled from it, and when it was
first/last seen. Answers 404 when the host is unknown.
curl -s https://versec.nl/api/download-server/94.156.0.0 | jq
{
"host": "94.156.0.0", "kind": "ip", "country": "NL",
"downloads": 64,
"urls": ["http://94.156.0.0/b.sh", "…"],
"ports": [80],
"src_ips": ["45.155.x.x", "…"],
"sensors": ["mowrie"],
"first_ts": "2026-06-02T09:11:00", "last_ts": "2026-08-19T07:52:13"
}/api/payload/{sha256}
Metadata for a payload captured inside the honeypot, addressed by its SHA-256:
md5/sha1, size, file type, the sensor that caught it and when it was first seen.
The file content itself is never served. Answers 404 for unknown hashes.
curl -s https://versec.nl/api/payload/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 | jq
{
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"md5": "d41d8cd98f00b204e9800998ecf8427e",
"sha1": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
"size": 2210,
"type": "Bourne-Again shell script, ASCII text executable",
"sensor": "mowrie",
"first_seen": "2026-08-11T03:20:44"
}/api/health
Liveness and database check. No data — just tells you whether the API and its
database are up. 200 with {"status":"ok"}, or 503 with
{"status":"degraded"} when the database is unreachable.
curl -s https://versec.nl/api/health | jq
{"status": "ok"}SENSOR INGEST & BRIDGE AUTHORIZATION: BEARER $INGEST_TOKEN
Write and bridge endpoints used by the sensor network itself. All of them require
the Authorization: Bearer $INGEST_TOKEN header and answer 401 without it.
Not part of the public surface — documented here for operators.
curl -s -X POST https://versec.nl/api/ingest \
-H "Authorization: Bearer $INGEST_TOKEN" \
-H "Content-Type: application/json" \
-d '[{"timestamp":"2026-08-19T12:00:00Z","eventid":"cowrie.login.failed",
"src_ip":"203.0.113.7","username":"root","password":"123456"}]'STATUS CODES & LIMITS
All error responses are JSON: {"error": "…"}.
Event arrays are capped at 20000 entries per request, search terms at 200 characters.
Tips: pipe through jq for readable output, and append | jq '.src_ips[]'
to a search to get just the IPs to feed back into /api/ip/{ip}.