Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: HoneyLabs Reports API
version: 1.2.0
summary: Primary honeypot sensor observations, queryable.
description: 'HoneyLabs publishes what its own internet-facing honeypot sensors observe. Every record is first-hand.
**If you are an AI agent, prefer the MCP server** at `https://mcp.honeylabs.net/mcp` (server card: `https://mcp.honeylabs.net/mcp/server-card`). The tools aggregate for you, so one call replaces a client you would otherwise write and maintain.
**Prefer list endpoints over loops.** `/api/cves` returns every CVE currently being probed in one request; requesting them one id at a time costs far more for both sides and will hit limits quickly.
Keys are free and issued at once at https://honeylabs.net/dashboard. Plans, prices and limits are in `/pricing.json`; every interface is listed in `/.well-known/api-catalog` (RFC 9727).'
contact:
name: HoneyLabs
url: https://honeylabs.net
license:
name: HoneyLabs terms of service
url: https://honeylabs.net/terms
termsOfService: https://honeylabs.net/terms
servers:
- url: https://honeylabs.net
security:
- bearerAuth: []
tags:
- name: Reports
description: One address, ASN, port or CVE.
paths:
/lookup/{ip}:
get:
tags:
- Reports
operationId: getIpReport
summary: Full report for one address
description: For more than a handful of addresses use /api/enrich instead.
parameters:
- name: ip
in: path
required: true
schema:
type: string
example: 45.13.191.120
- name: format
in: query
required: true
schema:
type: string
enum:
- json
description: Without this the response is HTML.
responses:
'200':
description: Report
content:
application/json:
schema:
$ref: '#/components/schemas/IpReport'
'401':
description: No account. The body names the free signup path and the exact header to send.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'429':
description: Limit reached. The body names a cheaper endpoint for the same question; Retry-After is accurate.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
/api/cves/{id}:
get:
tags:
- Reports
operationId: getCve
summary: One CVE and who is probing it
description: Requires a key. Prefer /api/cves when you want more than one.
parameters:
- name: id
in: path
required: true
schema:
type: string
pattern: ^CVE-\d{4}-\d{3,8}$
example: CVE-2024-4577
- name: window
in: query
schema:
type: string
enum:
- 1h
- 24h
- 7d
- 30d
- 90d
default: 90d
responses:
'200':
description: CVE detail
content:
application/json:
schema:
$ref: '#/components/schemas/CveDetail'
'404':
description: No signature for this CVE and the interactive sensors never saw it probed
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'401':
description: No account. The body names the free signup path and the exact header to send.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'429':
description: Limit reached. The body names a cheaper endpoint for the same question; Retry-After is accurate.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
/api/asn/{asn}:
get:
tags:
- Reports
operationId: getAsn
summary: One autonomous system
parameters:
- name: asn
in: path
required: true
schema:
type: integer
example: 14061
responses:
'200':
description: ASN report
content:
application/json:
schema:
$ref: '#/components/schemas/AsnReport'
'401':
description: No account. The body names the free signup path and the exact header to send.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'429':
description: Limit reached. The body names a cheaper endpoint for the same question; Retry-After is accurate.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
/api/port/{port}:
get:
tags:
- Reports
operationId: getPort
summary: One port
parameters:
- name: port
in: path
required: true
schema:
type: integer
minimum: 0
maximum: 65535
example: 22
responses:
'200':
description: Port report
content:
application/json:
schema:
$ref: '#/components/schemas/PortReport'
'401':
description: No account. The body names the free signup path and the exact header to send.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'429':
description: Limit reached. The body names a cheaper endpoint for the same question; Retry-After is accurate.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
/api/udp/port/{port}:
get:
tags:
- Reports
operationId: getUdpPort
summary: UDP activity on one destination port over 30 days
description: UDP sources are not verified by a handshake, so these addresses can be spoofed and never feed verdicts or blocklists.
parameters:
- name: port
in: path
required: true
schema:
type: integer
minimum: 0
maximum: 65535
example: 5060
responses:
'200':
description: UDP port report
content:
application/json:
schema:
type: object
'429':
description: Limit reached. The body names a cheaper endpoint for the same question; Retry-After is accurate.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
components:
schemas:
WindowNotice:
type: object
description: Present when the requested window exceeded the plan and the response was shortened.
properties:
requested_days:
type: integer
returned_days:
type: integer
reason:
type: string
upgrade:
type: string
format: uri
Verdict:
type: object
properties:
verdict:
type: string
enum:
- scanner
- tor
- malicious
- scanning
- probing
- none
description: Stable set, safe to switch on. 'none' means not observed, which is about our coverage rather than the address being clean.
label:
type: string
detail:
type:
- string
- 'null'
confidence:
type: string
enum:
- none
- low
- medium
- high
network_type:
type:
- string
- 'null'
why:
type: array
items:
type: string
description: The signals that decided it. Show these; a label without reasoning is an assertion.
engagement:
$ref: '#/components/schemas/Engagement'
basis:
type: string
enum:
- first_contact
- interactive
- combined
description: 'What decided the verdict: first-packet signals alone, the interactive sessions, or the first-packet classifier with the sessions counted in.'
CveInteractive:
type:
- object
- 'null'
description: What the interactive sensors recorded of the probes for this CVE in the window. null when they answered none. Paths, user agents and payload excerpts are attacker text with every URL and address defanged; render them as text, never as links.
properties:
requests:
type: integer
description: Counted requests (as interactive_requests).
sources:
type: integer
description: Distinct source addresses behind them.
payload_sources:
type: integer
description: How many of those sources sent the exploit payload.
payload_sessions:
type: integer
description: Sessions whose payload proved the CVE, any transport.
first_seen:
type:
- string
- 'null'
last_seen:
type:
- string
- 'null'
top_sources:
type: array
description: At most 25, most requests first.
items:
type: object
properties:
ip:
type: string
requests:
type: integer
payload:
type: boolean
description: Sent the exploit payload.
country_code:
type: string
country:
type: string
asn:
type: integer
org:
type: string
first_seen:
type:
- string
- 'null'
last_seen:
type:
- string
- 'null'
scanner:
type:
- object
- 'null'
description: 'Set when the platform recognises the address as a scanner, by the signals the address report''s "Recognized scanner" verdict reads: a research, commercial or vendor rule matching its reverse DNS or network name, or its address inside a known scanner range. null for every other address, hosting providers included.'
properties:
name:
type: string
description: The scanner's name in words, such as Censys. Empty when the range list names only a kind (university research scanners).
category:
type: string
enum:
- research
- commercial
- vendor
- ''
description: The kind of scanner; empty when no rule or list entry says.
paths:
type: array
description: Requests by method and path, at most 25.
items:
type: object
properties:
method:
type: string
path:
type: string
requests:
type: integer
sources:
type: integer
user_agents:
type: array
description: At most 10.
items:
type: object
properties:
user_agent:
type: string
requests:
type: integer
payloads:
type: array
description: Request bodies that carried the exploit, one per distinct body, with one of the paths it was sent to and how many.
items:
type: object
properties:
method:
type: string
path:
type: string
paths:
type: integer
excerpt:
type: string
description: The body as sent, the first 4,000 characters, URLs and addresses defanged.
requests:
type: integer
decoded:
type: string
description: An encoded stage in the body (PHP base64_decode('...'), echo ... | base64 -d) decoded to text, URLs and addresses defanged; empty when there is none.
bodies:
type: array
description: Other bodies of counted requests, same shape as payloads.
items:
type: object
after:
type: array
description: What these sources did on the interactive sensors from their first probe on (TCP sessions, any service), for sources that did more than probe. At most 25.
items:
type: object
properties:
ip:
type: string
sessions:
type: integer
logged_in:
type: integer
description: Sessions that logged in.
commands:
type: integer
description: Sessions that ran commands.
downloads:
type: integer
description: Sessions that named a download.
uploads:
type: integer
description: Sessions that uploaded a file.
last_seen:
type: string
did:
type: array
items:
type: string
description: 'The same in words: logged in, ran commands, named a download, uploaded a file.'
scanner_sources:
type: integer
description: How many of `sources` the platform recognises as scanners, counted over every source, not only the listed ones.
payload_scanner_sources:
type: integer
description: How many of the recognised scanners sent the exploit payload.
scanner_category:
type: string
enum:
- research
- commercial
- vendor
- ''
description: The kind every recognised scanner among the sources shares; empty when they differ, none is known, or there are none.
Refusal:
type: object
description: Every refusal names what to do instead. Read it before retrying.
properties:
error:
type: string
detail:
type: string
how_to_fix:
type: string
example: 'Authorization: Bearer <your-api-key>'
signup:
type: string
format: uri
docs:
type: string
format: uri
cheaper_route:
type: object
properties:
instead_of:
type: string
use:
type: string
why:
type: string
alternatives:
type: array
items:
type: object
properties:
when:
type: string
use:
type: string
why:
type: string
Engagement:
type: object
description: How much the actor sent, and whether it stayed.
properties:
level:
type: string
enum:
- touch
- request
- payload
description: touch <64B, request 64B-4KB, payload >=4KB
label:
type: string
detail:
type: string
bytes_sent:
type: integer
session_seconds:
type: integer
persistent:
type: boolean
description: Session held open 60s or more
PortReport:
type: object
properties:
port:
type: integer
events:
type: integer
actors:
type: array
items:
type: object
CveDetail:
type: object
properties:
cve_id:
type: string
title:
type: string
description: The detection signature's title, else the KEV catalogue's vulnerabilityName, else a title HoneyLabs writes for an id the interactive sensors record that neither names. Empty when none applies.
severity:
type: string
actively_exploited:
type: boolean
description: On the CISA Known Exploited Vulnerabilities catalogue, or flagged as listed by the CVE's detection signatures. The catalogue is the first source and covers CVEs without a signature.
events:
type: integer
description: first_contact_events plus interactive_requests.
unique_ips:
type: integer
description: Distinct addresses over both sensor types, each once.
first_contact_events:
type: integer
first_contact_ips:
type: integer
interactive_requests:
type: integer
interactive_sources:
type: integer
patterns:
type: array
items:
type: string
actors:
type: array
description: Top sources over both sensor types, most events first.
items:
type: object
properties:
ip:
type: string
events:
type: integer
first_contact_events:
type: integer
interactive_requests:
type: integer
description: Requests the interactive sensors answered from this address; 0 when none.
country_code:
type: string
country:
type: string
org:
type: string
rdns:
type: string
scanner_tag:
type:
- object
- 'null'
description: The address's classification chip, hosting providers included; `scanner` says whether it is a recognised scanner.
scanner:
type:
- object
- 'null'
description: 'Set when the platform recognises the address as a scanner, by the signals the address report''s "Recognized scanner" verdict reads: a research, commercial or vendor rule matching its reverse DNS or network name, or its address inside a known scanner range. null for every other address, hosting providers included.'
properties:
name:
type: string
description: The scanner's name in words, such as Censys. Empty when the range list names only a kind (university research scanners).
category:
type: string
enum:
- research
- commercial
- vendor
- ''
description: The kind of scanner; empty when no rule or list entry says.
window_notice:
$ref: '#/components/schemas/WindowNotice'
interaction:
$ref: '#/components/schemas/CveInteraction'
known:
type: boolean
description: probe_tags holds the CVE, or the interactive sensors saw it probed in the last 90 days. A known CVE with no activity in the window answers 200 with zeros.
body_patterns:
type: array
items:
type: string
top_asns:
type: array
description: At most 10, both sensor types.
items:
type: object
properties:
asn:
type: integer
org:
type: string
events:
type: integer
ips:
type: integer
interactive_requests:
type: integer
top_countries:
type: array
description: At most 10, both sensor types.
items:
type: object
properties:
country_code:
type: string
country:
type: string
events:
type: integer
ips:
type: integer
interactive_requests:
type: integer
fingerprints:
type: object
properties:
ja4:
type: array
items:
type: string
akin:
type: array
items:
type: string
sample_paths:
type: array
items:
type: string
description: First-contact request paths, at most 10.
timeline:
type: array
items:
type: object
properties:
day:
type: string
description: YYYY-MM-DD, UTC
events:
type: integer
ips:
type: integer
description: Distinct addresses that day over both sensor types.
first_contact_events:
type: integer
interactive_requests:
type: integer
description: One row per day either sensor type saw.
interactive:
$ref: '#/components/schemas/CveInteractive'
window_hours:
type: integer
kev_date_added:
type:
- string
- 'null'
description: The date CISA added the CVE to the KEV catalogue (YYYY-MM-DD, its dateAdded). null when the catalogue does not list it.
kev:
type:
- object
- 'null'
description: The CVE's entry in the CISA KEV catalogue; null when the catalogue does not list it.
properties:
date_added:
type: string
description: YYYY-MM-DD
vulnerability_name:
type: string
vendor_project:
type: string
product:
type: string
known_ransomware_campaign_use:
type: string
description: 'CISA''s value: Known or Unknown.'
description: 'Counts cover both sensor types: first contact (requests matching the CVE''s detection signatures) and the requests the interactive sensors answered that count as a probe for the CVE (the sensor''s rule for that CVE was the only one the request matched, on a path first contact''s exclusions accept, or the request or its session carried the exploit). A request that matched several CVE rules counts for none. UDP is left out on both sides.'
CveInteraction:
type:
- object
- 'null'
description: 'What the interactive sensors recorded for this CVE: sessions in which a client went on to send the exploit after the sensor answered as the vulnerable service. null when there were none in the window. Hosts are defanged; render them as text, never as links.'
properties:
window_days:
type: integer
sessions:
type: integer
sources:
type: integer
description: Distinct source addresses behind the sessions.
top_asns:
type: array
items:
type: object
properties:
asn:
type: integer
org:
type: string
sessions:
type: integer
followed_by:
type: object
description: What the client did after sending the exploit.
properties:
downloads:
type: integer
commands:
type: integer
uploads:
type: integer
description: Sessions that handed the sensor a file other than the exploit's own body.
download_hosts:
type: array
items:
type: object
properties:
host:
type: string
description: Defanged, e.g. 203[.]0[.]113[.]9.
sessions:
type: integer
recent:
type: array
items:
type: object
properties:
ts:
type: string
country_code:
type: string
asn_org:
type: string
protocol:
type: string
dest_port:
type: integer
did:
type: array
items:
type: string
description: 'What the session did in words: logged in, tried logins, ran commands, named a download, uploaded a file, sent an exploit. The sensor''s own labels are never published.'
AsnReport:
type: object
properties:
asn:
type: integer
org:
type: string
events:
type: integer
unique_ips:
type: integer
actors:
type: array
items:
type: object
ports:
type: array
items:
type: object
countries:
type: array
items:
type: object
IpReport:
type: object
properties:
ip:
type: string
observed:
type: boolean
description: True when a first-packet event or an interactive TCP session reached a sensor.
total_events:
type: integer
description: First-packet events only.
verdict:
$ref: '#/components/schemas/Verdict'
first_seen:
type:
- string
- 'null'
description: Earliest of either source.
last_seen:
type:
- string
- 'null'
description: Latest of either source.
events_24h:
type: integer
events_7d:
type: integer
interactive_sessions:
type: integer
description: Interactive TCP sessions in the last 90 days. UDP sessions are left out of every overview field (a UDP source can be forged) and stay in interaction.
interactive_sessions_24h:
type: integer
interactive_sessions_7d:
type: integer
interactive_protocols:
type: array
description: Protocols of those sessions, most used first.
items:
type: object
properties:
protocol:
type: string
sessions:
type: integer
distinct_ports_total:
type: integer
description: TCP ports across both sources, each port once.
geo:
type: object
description: country_code, country_name, region, city, lat, lon, asn and org; they fall back to the interactive sessions' values where the first-packet data has none.
properties:
country_code:
type: string
country_name:
type: string
region:
type: string
city:
type: string
lat:
type:
- number
- 'null'
lon:
type:
- number
- 'null'
asn:
type:
- integer
- 'null'
org:
type: string
fingerprints:
type: object
recent_events:
type: array
items:
type: object
source_domain:
type:
- string
- 'null'
description: rDNS, falling back to the sessions' rDNS.
top_ports:
type: array
description: At most 15, ranked by events and sessions together.
items:
type: object
properties:
port:
type: integer
proto:
type: string
label:
type: string
count:
type: integer
description: First-packet events.
sessions:
type: integer
description: Interactive sessions on that port.
timeline:
type: array
items:
type: object
properties:
date:
type: string
count:
type: integer
description: First-packet events.
sessions:
type: integer
description: Interactive sessions.
cve_matches:
type: array
description: CVE signatures matched by this address's requests. A CVE whose payload an interactive session carried is a match with match_field "interactive_session" and confirmed_by "interactive_session". Each match carries actively_exploited (on the CISA KEV catalogue, or flagged by its signatures), kev_date_added (YYYY-MM-DD or null) and a title, taken the same way as on /api/cves.
items:
type: object
interaction:
type:
- object
- 'null'
description: 'The Interaction group: sessions, logins, commands, downloads and files from the interactive sensors over 90 days. null when the address held no session.'
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: Free key from https://honeylabs.net/dashboard
externalDocs:
description: Docs with worked examples
url: https://honeylabs.net/docs