{"openapi":"3.1.0","info":{"title":"Fail2ban observations API","version":"1.0.0","description":"Snapshot observations from Fail2ban sources. A recorded IP is not proof of malicious activity. Timestamps are collection times, not exact BAN-event times.","license":{"name":"BSD-2-Clause","identifier":"BSD-2-Clause"}},"servers":[{"url":"https://api.f2b.0xhw.com","description":"Public read API"}],"paths":{"/health":{"get":{"summary":"Service metadata","parameters":[],"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}},"400":{"$ref":"#/components/responses/InvalidQuery"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}},"operationId":"getHealth","tags":["Public API"]}},"/v1/lookup":{"get":{"summary":"IP observations including inactive and stale records","parameters":[{"name":"ip","in":"query","required":true,"schema":{"type":"string"},"description":"Public IPv4 or IPv6 address. Private and reserved addresses are rejected; IPv4-mapped IPv6 is normalized.","example":"1.1.1.1"}],"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Lookup"}}}},"400":{"$ref":"#/components/responses/InvalidQuery"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}},"operationId":"lookupIP","tags":["Public API"]}},"/v1/ips":{"get":{"summary":"Paged observations with filters, sorting and cursor pagination","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":500,"default":100}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":2048},"description":"Opaque cursor from next_cursor or X-Next-Cursor. It is bound to the filters and sort; reuse with different conditions returns 400."},{"name":"source","in":"query","required":false,"schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{0,63}$"}},{"name":"jail","in":"query","required":false,"schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{0,63}$"}},{"name":"country","in":"query","required":false,"schema":{"type":"string","pattern":"^[A-Z]{2}$"}},{"name":"asn","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":4294967295}},{"name":"active","in":"query","required":false,"schema":{"type":"string","enum":["true","false","all"],"default":"true"},"description":"true: fresh active observations; false: inactive observations; all: full history including stale sources."},{"name":"stale","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]},"description":"true only for sources whose last collection is older than the stale threshold."},{"name":"ip_prefix","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":40,"pattern":"^[0-9a-fA-F:.]+$"},"description":"Text prefix match on the canonical IP address."},{"name":"organization","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":128},"description":"Case-insensitive substring match on the organization name."},{"name":"first_seen_from","in":"query","required":false,"schema":{"type":"integer","minimum":0,"maximum":100000000000},"description":"Unix seconds; inclusive lower bound."},{"name":"first_seen_to","in":"query","required":false,"schema":{"type":"integer","minimum":0,"maximum":100000000000},"description":"Unix seconds; inclusive upper bound."},{"name":"last_seen_from","in":"query","required":false,"schema":{"type":"integer","minimum":0,"maximum":100000000000},"description":"Unix seconds; inclusive lower bound on the effective last-seen time."},{"name":"last_seen_to","in":"query","required":false,"schema":{"type":"integer","minimum":0,"maximum":100000000000},"description":"Unix seconds; inclusive upper bound on the effective last-seen time."},{"name":"sort","in":"query","required":false,"schema":{"type":"string","enum":["ip","source","jail","country","asn","organization","active","first_seen","last_seen"],"default":"ip"},"description":"Sort key. Empty country and organization values sort before non-empty values; a missing ASN sorts as 0."},{"name":"order","in":"query","required":false,"schema":{"type":"string","enum":["asc","desc"],"default":"asc"}}],"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ObservationPage"}}}},"400":{"$ref":"#/components/responses/InvalidQuery"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}},"operationId":"listObservations","tags":["Public API"],"description":"Filter, sort and page observations. Ties are broken by ip, source and jail so that pages never duplicate or skip rows."}},"/v1/stats":{"get":{"summary":"Filtered counts and per-column aggregates","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":500,"default":100}},{"name":"source","in":"query","required":false,"schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{0,63}$"}},{"name":"jail","in":"query","required":false,"schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{0,63}$"}},{"name":"country","in":"query","required":false,"schema":{"type":"string","pattern":"^[A-Z]{2}$"}},{"name":"asn","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":4294967295}},{"name":"active","in":"query","required":false,"schema":{"type":"string","enum":["true","false","all"],"default":"true"},"description":"true: fresh active observations; false: inactive observations; all: full history including stale sources."},{"name":"stale","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]},"description":"true only for sources whose last collection is older than the stale threshold."},{"name":"ip_prefix","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":40,"pattern":"^[0-9a-fA-F:.]+$"},"description":"Text prefix match on the canonical IP address."},{"name":"organization","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":128},"description":"Case-insensitive substring match on the organization name."},{"name":"first_seen_from","in":"query","required":false,"schema":{"type":"integer","minimum":0,"maximum":100000000000},"description":"Unix seconds; inclusive lower bound."},{"name":"first_seen_to","in":"query","required":false,"schema":{"type":"integer","minimum":0,"maximum":100000000000},"description":"Unix seconds; inclusive upper bound."},{"name":"last_seen_from","in":"query","required":false,"schema":{"type":"integer","minimum":0,"maximum":100000000000},"description":"Unix seconds; inclusive lower bound on the effective last-seen time."},{"name":"last_seen_to","in":"query","required":false,"schema":{"type":"integer","minimum":0,"maximum":100000000000},"description":"Unix seconds; inclusive upper bound on the effective last-seen time."},{"name":"group_by","in":"query","required":false,"schema":{"type":"string","enum":["country","asn","jail","source"]},"description":"Return a single aggregate list grouped by this column."},{"name":"sort","in":"query","required":false,"schema":{"type":"string","enum":["observations","ips","value"],"default":"observations"},"description":"Sort the group_by list."},{"name":"order","in":"query","required":false,"schema":{"type":"string","enum":["asc","desc"],"default":"desc"}}],"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Stats"}}}},"400":{"$ref":"#/components/responses/InvalidQuery"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}},"operationId":"getStats","tags":["Public API"],"description":"Apply the same filters as /v1/ips. Without group_by, return counts grouped by country, ASN, jail and source. With group_by, return a single sorted aggregate list."}},"/v1/sources":{"get":{"summary":"Public source and jail collection status","parameters":[],"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SourceList"}}}},"400":{"$ref":"#/components/responses/InvalidQuery"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}},"operationId":"listSources","tags":["Public API"]}},"/v1/feed.txt":{"get":{"summary":"Deduplicated IPs with the same filters, ordered by IP","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":5000,"default":1000}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":2048},"description":"Opaque cursor from next_cursor or X-Next-Cursor. It is bound to the filters and sort; reuse with different conditions returns 400."},{"name":"source","in":"query","required":false,"schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{0,63}$"}},{"name":"jail","in":"query","required":false,"schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{0,63}$"}},{"name":"country","in":"query","required":false,"schema":{"type":"string","pattern":"^[A-Z]{2}$"}},{"name":"asn","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":4294967295}},{"name":"active","in":"query","required":false,"schema":{"type":"string","enum":["true","false","all"],"default":"true"},"description":"true: fresh active observations; false: inactive observations; all: full history including stale sources."},{"name":"stale","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]},"description":"true only for sources whose last collection is older than the stale threshold."},{"name":"ip_prefix","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":40,"pattern":"^[0-9a-fA-F:.]+$"},"description":"Text prefix match on the canonical IP address."},{"name":"organization","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":128},"description":"Case-insensitive substring match on the organization name."},{"name":"first_seen_from","in":"query","required":false,"schema":{"type":"integer","minimum":0,"maximum":100000000000},"description":"Unix seconds; inclusive lower bound."},{"name":"first_seen_to","in":"query","required":false,"schema":{"type":"integer","minimum":0,"maximum":100000000000},"description":"Unix seconds; inclusive upper bound."},{"name":"last_seen_from","in":"query","required":false,"schema":{"type":"integer","minimum":0,"maximum":100000000000},"description":"Unix seconds; inclusive lower bound on the effective last-seen time."},{"name":"last_seen_to","in":"query","required":false,"schema":{"type":"integer","minimum":0,"maximum":100000000000},"description":"Unix seconds; inclusive upper bound on the effective last-seen time."}],"responses":{"200":{"description":"Successful result","content":{"text/plain":{"schema":{"type":"string","description":"Canonical IP addresses, one per line, with a trailing newline unless empty."}}},"headers":{"X-Truncated":{"description":"true if another page is available; otherwise false.","schema":{"type":"string","enum":["true","false"]}},"X-Next-Cursor":{"description":"Only present when X-Truncated is true. Pass as the next cursor query parameter.","schema":{"type":"string"}}}},"400":{"$ref":"#/components/responses/InvalidQuery"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}},"operationId":"getIPFeed","tags":["Public API"],"description":"Return one IP per line. The cursor is bound to the filters; reuse with different conditions returns 400."}}},"components":{"schemas":{"Observation":{"type":"object","required":["source","jail","ip","first_seen","last_seen","active","country","asn","organization","network","stale"],"properties":{"source":{"type":"string"},"jail":{"type":"string"},"ip":{"type":"string","description":"Canonical public IPv4 or IPv6 address."},"first_seen":{"type":"integer","minimum":0,"description":"Unix timestamp in seconds (collection time, not an exact BAN event time)."},"last_seen":{"type":"integer","minimum":0,"description":"Unix timestamp in seconds (collection time, not an exact BAN event time)."},"active":{"type":"integer","enum":[0,1],"description":"1 when present in the latest snapshot; 0 when removed."},"country":{"type":["string","null"],"pattern":"^[A-Z]{2}$"},"asn":{"type":["integer","null"],"minimum":1,"maximum":4294967295},"organization":{"type":["string","null"]},"network":{"type":["string","null"]},"stale":{"type":"integer","enum":[0,1],"description":"1 when the jail has not been collected within the freshness window (48 hours by default)."}},"additionalProperties":false},"Counts":{"type":"object","required":["observations","ips"],"properties":{"observations":{"type":"integer","minimum":0},"ips":{"type":"integer","minimum":0}},"additionalProperties":false},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}},"additionalProperties":false},"Health":{"type":"object","required":["service","version","status","classification","html_reports"],"properties":{"service":{"type":"string","const":"f2b-api"},"version":{"type":"integer","const":1},"status":{"type":"string","const":"ok"},"classification":{"type":"string"},"html_reports":{"type":"string","format":"uri"}},"additionalProperties":false},"Lookup":{"type":"object","required":["ip","observations","truncated"],"properties":{"ip":{"type":"string"},"observations":{"type":"array","items":{"$ref":"#/components/schemas/Observation"},"maxItems":1000},"truncated":{"type":"boolean"}},"additionalProperties":false},"ObservationPage":{"type":"object","required":["observations","next_cursor"],"properties":{"observations":{"type":"array","items":{"$ref":"#/components/schemas/Observation"},"maxItems":500},"next_cursor":{"type":["string","null"],"description":"Opaque pagination cursor; retain the same filters on subsequent requests."}},"additionalProperties":false},"SourceState":{"type":"object","required":["source","jail","collected_at","currently_banned","total_banned","public_ip_count","stale"],"properties":{"source":{"type":"string"},"jail":{"type":"string"},"collected_at":{"type":"integer","minimum":0,"description":"Unix timestamp in seconds (collection time, not an exact BAN event time)."},"currently_banned":{"type":"integer","minimum":0},"total_banned":{"type":"integer","minimum":0},"public_ip_count":{"type":"integer","minimum":0},"stale":{"type":"integer","enum":[0,1]}},"additionalProperties":false},"SourceList":{"type":"object","required":["sources"],"properties":{"sources":{"type":"array","items":{"$ref":"#/components/schemas/SourceState"},"maxItems":1000}},"additionalProperties":false},"Stats":{"type":"object","required":["active"],"properties":{"active":{"$ref":"#/components/schemas/Counts"},"countries":{"type":"array","items":{"type":"object","required":["value","observations","ips"],"properties":{"value":{"type":["string","null"]},"observations":{"type":"integer","minimum":0},"ips":{"type":"integer","minimum":0}},"additionalProperties":false},"maxItems":100},"asns":{"type":"array","items":{"type":"object","required":["value","observations","ips"],"properties":{"value":{"type":["integer","null"]},"observations":{"type":"integer","minimum":0},"ips":{"type":"integer","minimum":0}},"additionalProperties":false},"maxItems":100},"jails":{"type":"array","items":{"type":"object","required":["value","observations","ips"],"properties":{"value":{"type":"string"},"observations":{"type":"integer","minimum":0},"ips":{"type":"integer","minimum":0}},"additionalProperties":false},"maxItems":100},"sources":{"type":"array","items":{"type":"object","required":["value","observations","ips"],"properties":{"value":{"type":"string"},"observations":{"type":"integer","minimum":0},"ips":{"type":"integer","minimum":0}},"additionalProperties":false},"maxItems":100},"group_by":{"type":"string","enum":["country","asn","jail","source"]},"sort":{"type":"string","enum":["observations","ips","value"]},"order":{"type":"string","enum":["asc","desc"]},"groups":{"type":"array","items":{"$ref":"#/components/schemas/StatsGroup"},"maxItems":500}},"additionalProperties":false},"StatsGroup":{"type":"object","required":["value","observations","ips"],"properties":{"value":{"type":["string","integer","null"]},"observations":{"type":"integer","minimum":0},"ips":{"type":"integer","minimum":0}},"additionalProperties":false}},"responses":{"InvalidQuery":{"description":"Invalid query parameter.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Per-client edge rate limit.","headers":{"Retry-After":{"description":"Seconds until retry.","schema":{"type":"integer","example":60}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unavailable":{"description":"Temporary service or free-quota limit failure.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}