DEVELOPER · READ-ONLY HTTP API

API Reference

BASEversec.nl/api AUTHNONE · PUBLIC READ FORMATJSON

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.

BASE URL
https://versec.nl/api
GET /api/ip/{ip} — full intelligence for one attacker IP
GET /api/search?q=… — free-text IOC search
GET /api/overview — global platform counters
GET /api/feed — newest attack events
GET /api/feed/geo — attack origins per country
GET /api/download-servers — malware distribution hosts
GET /api/download-server/{host} — one distribution host
GET /api/payload/{sha256} — captured payload metadata
GET /api/health — liveness check

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.

PUBLIC ENDPOINTS — READ ONLY
GET /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.

REQUEST
curl -s https://versec.nl/api/ip/132.148.73.100 | jq
RESPONSE · 200 (abbreviated)
{
  "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": "…"}]
}
GET /api/overview

Global platform counters — the same numbers shown on the landing page. Good for a quick sanity check that data is flowing.

REQUEST
curl -s https://versec.nl/api/overview | jq
RESPONSE · 200
{
  "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"
}
GET /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.

limit — optional · 1–200 · default 40
REQUEST
curl -s 'https://versec.nl/api/feed?limit=10' | jq
RESPONSE · 200 (abbreviated)
{
  "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": "…"
    }
  ]
}
GET /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.

REQUEST
curl -s https://versec.nl/api/feed/geo | jq
RESPONSE · 200 (abbreviated)
{
  "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": "…"}
  ]
}
GET /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.

limit — optional · 1–1000 · default 200
REQUEST
curl -s 'https://versec.nl/api/download-servers?limit=20' | jq
RESPONSE · 200 (abbreviated)
{
  "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"
    }
  ]
}
GET /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.

REQUEST
curl -s https://versec.nl/api/download-server/94.156.0.0 | jq
RESPONSE · 200 (abbreviated)
{
  "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"
}
GET /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.

REQUEST
curl -s https://versec.nl/api/payload/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 | jq
RESPONSE · 200
{
  "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"
}
GET /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.

REQUEST
curl -s https://versec.nl/api/health | jq
RESPONSE · 200
{"status": "ok"}
INTERNAL ENDPOINTS — BEARER TOKEN

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.

POST /api/ingest — cowrie events from remote honeypot sensors · JSON array, ≤ 20000 events, ≤ 4 MB
POST /api/audit — auditd events from endpoint hosts (audit dashboard) · JSON array, ≤ 20000, ≤ 2 MB
POST /api/payload — upload a captured payload (base64 content, upsert on sha256) · ≤ 3 MB
GET /api/bridge/pull — remote honeypot pulls pending ban / quarantine requests
POST /api/bridge/ack — acknowledge pulled files · {"names": ["…"]}
POST /api/bridge/bans/sync — honeypot mirrors its locally applied bans back to the dashboard
EXAMPLE · INGEST
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"}]'
ERRORS & LIMITS

STATUS CODES & LIMITS

400 — invalid ip · missing q · bad host · bad shasum · bad bodyclient error
401 — missing or wrong Bearer token (internal endpoints)unauthorized
404 — unknown download-server host or payload hashnot found
413 — body too large: ingest 4 MB · audit 2 MB · payload 3 MBtoo large
503 — /api/health: database unreachabledegraded

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}.