Developer Documentation
BadBot Gateway API
Use BadBot Gateway to look up IP and domain reputation, submit abuse reports, consume high-risk feeds, and correlate suspicious indicators.
https://gateway.badbot.net
Quickstart
Start with a health check, create or generate an API key, submit a report, then query reputation for the same indicator.
1. Health check
curl https://gateway.badbot.net/api/v1/health
# → {"status":"ok","time":"2026-05-12T10:00:00+00:00"}
2. Get an API key
Sign in and generate a personal key under My API Keys. Copy it immediately; it is shown only once.
export BADBOT_API_KEY="paste-your-key-here"
3. Submit an abuse report
curl -X POST https://gateway.badbot.net/api/v1/report \
-H "Content-Type: application/json" \
-H "X-Api-Key: $BADBOT_API_KEY" \
-d '{
"indicator": "198.51.100.23",
"category_id": 1,
"severity": 6,
"comment": "Credential stuffing"
}'
# → {"status":"reported","id":42}
4. Query reputation
curl https://gateway.badbot.net/api/v1/reputation/198.51.100.23
Authentication
Write endpoints require the X-Api-Key request header.
Authenticated users can generate and revoke personal API keys from /api-keys. User API keys authorize report submission and feed access just like other API keys.
Admin-token endpoints use X-Admin-Token and are intended for operators, not normal client integrations.
Read-only endpoints such as health, reputation, search, suggestions, feeds, and correlation can be used without an API key unless deployment policy changes.
Endpoint Overview
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/v1/reputation/{indicator} | — | Look up score, reports, categories, and last report time. |
| GET | /api/v1/search | — | Search indicators with pagination and country filter. |
| GET | /api/v1/suggest | — | Autocomplete prefix search for indicators. |
| POST | /api/v1/report | API key | Submit one abuse report. |
| POST | /api/v1/report/bulk | API key | Submit many reports in one request. |
| GET | /api/v1/feeds/high-risk | — | Fetch high-risk indicators as JSON, CSV, or TXT. |
| GET | /api/v1/correlation/{indicator} | — | Find related indicators through ASN and reporter overlap. |
/api/v1/report
API key required
Submit Report
Submit a single abuse report for an IP address or domain.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| indicator | string | yes | IP address or domain name (max 253 chars). |
| category_id | integer | yes | Abuse category ID. See categories. |
| severity | integer | no | 1–10 (default: 1). Higher values increase the reputation score. |
| comment | string | no | Free-text note (max 500 chars). |
Example request
curl -X POST https://gateway.badbot.net/api/v1/report \
-H "Content-Type: application/json" \
-H "X-Api-Key: $BADBOT_API_KEY" \
-d '{
"indicator": "198.51.100.23",
"category_id": 1,
"severity": 6,
"comment": "Credential stuffing"
}'
Response 201
{
"status": "reported",
"id": 42
}
/api/v1/report/bulk
API key required
Bulk Report
Submit up to 1 000 reports in one request. The request body can be a JSON array or an object with a reports key. Each item uses the same fields as a single report. Invalid rows are collected in the errors array without blocking valid ones.
Example request
curl -X POST https://gateway.badbot.net/api/v1/report/bulk \
-H "Content-Type: application/json" \
-H "X-Api-Key: $BADBOT_API_KEY" \
-d '[
{"indicator":"198.51.100.23","category_id":1,"severity":6},
{"indicator":"evil.example.com","category_id":9,"severity":8}
]'
Response 201
{
"created": 2,
"errors": [],
"total": 2
}
Partial failure
{
"created": 1,
"errors": [
{"index": 1, "error": "Category 99 does not exist"}
],
"total": 2
}
/api/v1/reputation/{indicator}
Reputation Lookup
Returns the reputation score, report count, category breakdown, and last report timestamp for an IP address or domain. Scores decay over time using a configurable half-life.
Example request
curl https://gateway.badbot.net/api/v1/reputation/198.51.100.23
Response 200
{
"indicator": "198.51.100.23",
"found": true,
"score": 42.5,
"total_reports": 3,
"categories": {
"Credential Stuffing": 3
},
"last_reported_at": "2026-01-01T12:00:00+00:00"
}
When the indicator is unknown, found is false and score is 0.
/api/v1/search
Search Indicators
Prefix search across all known indicators with pagination, sorting, and optional country filter.
Query parameters
| Param | Type | Default | Description |
|---|---|---|---|
| q | string | — | Search query (required, 1–100 chars). |
| limit | integer | 20 | Results per page (1–100). |
| offset | integer | 0 | Pagination offset (0–10 000). |
| sort | string | value | value (alphabetical) or recent (newest first). |
| country | string | — | 2-letter ISO country code (e.g. US, NL). Filters IP indicators only. |
Example request
curl "https://gateway.badbot.net/api/v1/search?q=198.51&sort=recent&limit=5"
Response 200
[
"198.51.100.23",
"198.51.100.44",
"198.51.200.1"
]
/api/v1/suggest
Autocomplete Suggestions
Lightweight prefix search returning indicator values and types. Useful for search-as-you-type UIs. When q is omitted, returns the most recently reported indicators.
Query parameters
| Param | Type | Default | Description |
|---|---|---|---|
| q | string | — | Prefix to match (optional, max 100 chars). |
| limit | integer | 10 | Max results (1–50). |
Example request
curl "https://gateway.badbot.net/api/v1/suggest?q=198.51&limit=3"
Response 200
[
{"value": "198.51.100.23", "type": "ip"},
{"value": "198.51.100.44", "type": "ip"},
{"value": "198.51.200.1", "type": "ip"}
]
/api/v1/correlation/{indicator}
Correlation
Find IP indicators related through shared ASN, reporter overlap, passive DNS, and activity patterns. Currently supports IP addresses only.
Example request
curl https://gateway.badbot.net/api/v1/correlation/198.51.100.23
Response 200 (abbreviated)
{
"seed": {
"indicator": "198.51.100.23",
"type": "ip",
"found": true,
"score": 42.5,
"total_reports": 3,
"meta": {
"asn": 64496,
"organization": "Example ISP",
"country": "US"
}
},
"matches": [...],
"infrastructure": {
"meta": {...},
"domains": [...]
},
"spread_summary": {...},
"activity_timeline": [...]
}
The response includes correlated matches, passive DNS infrastructure, activity timelines, and regional spread data.
Feeds
Feeds are available through stable web aliases and the API route. Use TXT for simple blocklists, CSV for imports, and JSON for structured automation.
Stable web aliases
API route query parameters
| Param | Type | Default | Description |
|---|---|---|---|
| min_score | float | 5 | Minimum reputation score to include. |
| limit | integer | 1 000 | Max results (1–10 000). |
| type | string | — | ipv4, ipv6, or domain. |
| format | string | json | json, csv, or txt. |
Example request
curl "https://gateway.badbot.net/api/v1/feeds/high-risk?type=ipv4&min_score=10&limit=100"
JSON response item
{
"indicator": "198.51.100.23",
"score": 42.5,
"reports": 3,
"country_iso": "US",
"asn": 64496
}
limit, the response includes
X-Truncated: true and X-Truncated-Limit response headers.
The full snapshot feeds (-full-ipv4.txt, -full-ipv6.txt) are not subject to the limit cap.
Abuse Categories
Every report requires a category_id. The default severity is used when you omit the severity field.
| ID | Category | Default Severity |
|---|---|---|
| 1 | Credential Stuffing | 3 |
| 2 | SMTP Abuse | 4 |
| 3 | Web App Attack | 6 |
| 4 | DDoS | 8 |
| 5 | Spam | 5 |
| 6 | Botnet Drone | 7 |
| 7 | Malware | 9 |
| 8 | Scanning | 2 |
| 9 | Phishing | 8 |
| 10 | Fraud | 7 |
| 11 | Web Probe | 3 |
| 12 | Config Exposure Probe | 6 |
| 13 | Web Stack Probe | 5 |
| 14 | Admin Panel Probe | 5 |
| 15 | API Probe | 5 |
Rate Limits
All endpoints are rate-limited per client IP. When a limit is exceeded the API returns 429 Too Many Requests with a Retry-After header.
| Endpoint | Limit |
|---|---|
| GET /api/v1/health | 30 / min |
| GET /api/v1/reputation/{indicator} | 120 / min |
| GET /api/v1/search | 60 / min |
| GET /api/v1/suggest | 120 / min |
| POST /api/v1/report | 30 / min |
| POST /api/v1/report/bulk | 10 / min |
| GET /api/v1/feeds/high-risk | 10 / min |
| GET /api/v1/correlation/{indicator} | 30 / min |
Errors
Error responses use a consistent JSON shape. The HTTP status code is mirrored in the body.
Error response format
{
"error": "Bad Request",
"message": "Category 99 does not exist",
"status": 400
}
Status codes
200success (reads).201success (writes).400invalid input or payload shape.401missing API key.403invalid API key or token.404resource not found.409duplicate or conflicting resource.429rate limit exceeded — seeRetry-Afterheader.
Troubleshooting
403 Invalid API key: regenerate the key if it was copied wrong or the API key salt changed.400 Category N does not exist: use one of the category IDs listed above.- Slow first report: enrichment may be performing RDAP, DNSBL, GeoIP, or WHOIS lookups in the background.
- Feed format errors: use
format=json|csv|txton the API route or the stable web aliases under Feeds.
Code Examples
Python requests
import os
import requests
base_url = "https://gateway.badbot.net"
api_key = os.environ["BADBOT_API_KEY"]
payload = {
"indicator": "198.51.100.23",
"category_id": 1,
"severity": 6,
"comment": "Credential stuffing",
}
response = requests.post(
f"{base_url}/api/v1/report",
json=payload,
headers={"X-Api-Key": api_key},
timeout=10,
)
response.raise_for_status()
reputation = requests.get(
f"{base_url}/api/v1/reputation/{payload['indicator']}",
timeout=10,
).json()
print(reputation)
JavaScript fetch
const baseUrl = "https://gateway.badbot.net";
const apiKey = process.env.BADBOT_API_KEY;
const report = {
indicator: "198.51.100.23",
category_id: 1,
severity: 6,
comment: "Credential stuffing",
};
const response = await fetch(`${baseUrl}/api/v1/report`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Api-Key": apiKey,
},
body: JSON.stringify(report),
});
if (!response.ok) {
throw new Error(`BadBot API error ${response.status}`);
}
PHP curl
<?php
$baseUrl = 'https://gateway.badbot.net';
$apiKey = getenv('BADBOT_API_KEY');
$payload = json_encode([
'indicator' => '198.51.100.23',
'category_id' => 1,
'severity' => 6,
'comment' => 'Credential stuffing',
]);
$ch = curl_init($baseUrl . '/api/v1/report');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-Api-Key: ' . $apiKey,
],
CURLOPT_POSTFIELDS => $payload,
CURLOPT_RETURNTRANSFER => true,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status >= 400) {
throw new RuntimeException('BadBot API error ' . $status . ': ' . $body);
}
Go net/http
package main
import (
"bytes"
"encoding/json"
"net/http"
"os"
)
func main() {
payload, _ := json.Marshal(map[string]any{
"indicator": "198.51.100.23",
"category_id": 1,
"severity": 6,
"comment": "Credential stuffing",
})
req, _ := http.NewRequest("POST", "https://gateway.badbot.net/api/v1/report", bytes.NewReader(payload))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Api-Key", os.Getenv("BADBOT_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
panic(resp.Status)
}
}
Machine-Readable Docs
Use these endpoints for generated clients, API discovery, and agent-friendly context.