bot protection for small online businesses

ip database · api

Reporting API

Report abusive IPs and domains from your own tools: fail2ban, a WAF, a mail server, a log script. One report per request, or up to 1,000 at once. API reports are ordinary reports: same categories, same scoring, same moderation.

Keys

Create a key on your API keys page (you need an account with a confirmed email). It's shown once; we keep only a hash of it. Send it in a header, never in the URL:

Authorization: Bearer rws_live_…

Use one key per tool. Each report remembers the key it came with, so you can revoke a leaked key and withdraw what it sent without touching your other reports.

One report: POST /api/v1/reports

curl https://rowebsec.com/api/v1/reports \
  -H "Authorization: Bearer $ROWEBSEC_KEY" \
  -H "Content-Type: application/json" \
  -d '{"target": "185.220.101.4", "categories": ["ssh"], "comment": "412 failed logins in 5 minutes"}'
FieldRequiredNotes
targetyesOne IPv4 or IPv6 address, or a domain. No ranges, no private addresses.
categoriesyes1–10 category slugs, exactly as listed: ["ssh", "brute-force"]
observed_atnoISO 8601 with a time zone, like 2026-11-06T14:03:00Z. Defaults to now; at most 30 days back.
commentnoUp to 1,000 characters. Public: no personal data.
evidencenoLog lines, up to 4,000 characters. Longer is rejected, not cut.
domainnoFor an IP: the domain involved (phishing host, spam sender).
anonymousnotrue hides your name on the report.

Answer, 201 Created:

{
  "report": { "id": "0193…", "observed_at": "2026-11-06T14:03:00Z", "categories": ["ssh"], "anonymous": false },
  "indicator": {
    "target": "185.220.101.4", "kind": "ip", "score": 87, "verdict": "high", "reports": 41,
    "score_pending": true, "whitelisted": false, "url": "https://rowebsec.com/ip/185.220.101.4"
  }
}

The score and report count are from before your report: scores are recalculated a few seconds later, once for a burst of reports, so a busy address doesn't slow your requests down. score_pending says a recalculation is queued; the address's page shows the new score shortly.

Reports on whitelisted addresses (search engine crawlers, public DNS…) are accepted but don't score; the answer says "whitelisted": true, "whitelisted_by": "Google", so your tool can stop reporting them.

Bulk: POST /api/v1/reports/bulk

Up to 1,000 reports and 2 MB per request, as JSON or CSV. Each item is the same as a single report, plus an optional ref (up to 64 characters) that comes back in the results.

curl https://rowebsec.com/api/v1/reports/bulk \
  -H "Authorization: Bearer $ROWEBSEC_KEY" \
  -H "Content-Type: application/json" \
  -d '{"reports": [
        {"ref": "a1", "target": "185.220.101.4", "categories": ["ssh"], "observed_at": "2026-11-06T14:03:00Z"},
        {"ref": "a2", "target": "evil.example", "categories": ["phishing"], "comment": "Fake bank login"}
      ]}'

CSV with a header row (target required; categories, observed_at, comment optional; the ref is the row number):

curl https://rowebsec.com/api/v1/reports/bulk \
  -H "Authorization: Bearer $ROWEBSEC_KEY" \
  -H "Content-Type: text/csv" --data-binary @reports.csv

target,categories,observed_at,comment
185.220.101.4,"ssh,brute-force",2026-11-06T14:03:00Z,sshd brute force
evil.example,phishing,,Fake bank login

Valid items are saved even when others fail. Answer, 200 OK:

{
  "submission_id": "0193…", "accepted": 1, "merged": 0, "duplicate": 0, "rejected": 1,
  "results": [
    { "index": 0, "ref": "a1", "status": "accepted", "report_id": "0193…", "target": "185.220.101.4" },
    { "index": 1, "ref": "a2", "status": "rejected", "error": { "code": "invalid_target", "message": "…", "field": "target" } }
  ]
}
  • accepted: saved as a report.
  • merged: the same target within 15 minutes earlier in the batch; combined into that report (categories and comments joined). One line per failed login doesn't make 400 reports.
  • duplicate: you reported this target in the last 15 minutes. Not an error; don't retry.
  • rejected: with an error, as below.

Scores and GeoIP for bulk reports are filled in a moment later. GET /api/v1/reports/bulk/:submission_id returns the same answer again for 7 days.

Errors

One shape everywhere:

{ "error": { "code": "invalid_target", "message": "10.0.0.5 is a private or reserved address…", "field": "target" } }
HTTPcodeWhen
400bad_requestThe body isn't valid JSON or CSV, or is empty
401unauthorizedMissing, unknown or revoked key, or a key in the URL
403forbiddenThe key lacks the reports:write scope, or the account is unconfirmed or banned
409duplicateYou reported this target in the last 15 minutes
413too_largeOver 1,000 items or 2 MB
422invalid_target, invalid_category, invalid_field, self_reportThe report breaks a rule; field says which
429rate_limited, daily_limitToo many requests or reports; wait for Retry-After seconds
503unavailableReporting through the API is paused

Limits

Per account, so more keys don't raise them. Web and API reports share the daily limit.

First weekConfirmedTrusted reporter
Reports per 24 hours1005,000100,000
Single-report requests per minute 30120600
Bulk requests per hour 660600

Every answer carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset; a 429 carries Retry-After. Volume doesn't raise a score: one reporter counts once per address per day.

Retries

Send an Idempotency-Key header (any string up to 64 characters) to retry safely after a timeout: the same key within 24 hours returns the first answer, with Idempotent-Replayed: true, instead of reporting again.

Categories

Use the slugs exactly as below. They never change. Also as JSON: GET /api/v1/categories (no key needed).

SlugCategoryFamily
brute-force Brute-Force Brute force
ftp-brute-force FTP Brute-Force Brute force
ssh SSH Brute-Force Brute force
bad-web-bot Bad Web Bot Web
sql-injection SQL Injection Web
web-app-attack Web App Attack Web
ddos DDoS Attack Network
iot-targeted IoT Targeted Network
port-scan Port Scan Network
spoofing Spoofing Network
email-spam Email Spam Spam
web-spam Web Spam Spam
fraud-orders Fraud Orders Fraud
fraud-voip Fraud VoIP Fraud
phishing Phishing Fraud
dns-compromise DNS Compromise Infrastructure
dns-poisoning DNS Poisoning Infrastructure
exploited-host Exploited Host Infrastructure
open-proxy Open Proxy informational, doesn't score Anonymizer
vpn VPN IP informational, doesn't score Anonymizer
hacking Hacking Other

fail2ban

Save as /etc/fail2ban/action.d/rowebsec.conf:

[Definition]
actionban = curl -fsS --max-time 10 https://rowebsec.com/api/v1/reports \
              -H "Authorization: Bearer <key>" -H "Content-Type: application/json" \
              -d '{"target": "<ip>", "categories": ["<category>"], "comment": "fail2ban <name>: banned after <failures> failures"}'

[Init]
key =
category = brute-force

Then add it to a jail in jail.local, with the category that fits the jail (sshd → ssh, postfix → email-spam, nginx-botsearch → web-app-attack):

[sshd]
enabled = true
action  = %(action_)s
          rowebsec[key="rws_live_…", category="ssh"]