Bot Detection Service

Block bots. Protect budget. Keep KPIs honest.

A single HTTP call scores each request in real time. Block known bots, review risky traffic, and safeguard pricing, signup, and checkout without any pixels or SDKs.

Edge-friendly Explainable decisions Minutes to integrate Works with CDNs & backends

Market reality

42% of traffic
is bot-driven while ~65% of those bots are malicious.
Source: Akamai (2024)
15–20% ad spend
is wasted on invalid traffic and “made-for-advertising” inventory.
Sources: ANA / SEL, Juniper
Continuous abuse
Scripted sign-ups, scraping & ATO skew funnels and create risk.
GenAI crawler pressure
AI bots (e.g., GPTBot, ClaudeBot) are surging, spiking bandwidth bills and forcing sites to throttle or block traffic.
Sources: Business Insider, WSJ
26% of bots originate from Residential ISPs
Bot operators make use of residential proxies making bots look human.
$3.60 lost per $1 of fraud
Chargebacks and manual fraud prevention/investigation multiply losses.

ROI snapshot

If you spend $100k/month on acquisition and conservatively block 15% invalid traffic pre-click or pre-request, that’s about $15,000/month in avoided waste plus cleaner optimization signals.

Benchmarks: Fraudblocker, ANA, Juniper
Lower CAC Higher ROAS Cleaner CVR Less ops noise

Why NoBotsPls?

🧩 HTTP-based bot detection

We read the signals every request already carries: headers, user-agent integrity, client hints and origin. No pixels, no SDK. One HTTP call in, a clear decision out.

  • Flags spoofed or broken headers instantly
  • Understands common bot frameworks & crawlers
  • Explainable reasons for each decision

🌐 IP reputation (VPN, Proxies, Residential Proxies)

Block traffic from known data centers, VPNs, open proxies-and the hard part: residential proxy networks used to mimic “real” users.

  • Datacenter & hosting ranges
  • VPN & anonymizer lists
  • Residential proxy CIDRs. Updated continuously

🤖 AI model + live-updated DB

A purpose-built model scores risk alongside continuously refreshed IP & crawler datasets. You get precision now and it gets smarter over time.

  • ML per-request risk and value scoring blended with rules
  • Fresh threat feeds & patterns, auto-refreshed
  • No lock-in: simple REST you control
`

No CAPTCHA. Keep real customers moving.

We don’t challenge your users. Challenge pages slow funnels and frustrate buyers. Cloudflare reported users spent ~32s on legacy challenges vs ~1s on their improved flow; the point stands: every second hurts conversion. Source: WIRED on CAPTCHA burden

Zero friction
No puzzles
B2B checkout safe

No pixels. No tag bloat. One HTTP call.

Third-party scripts are a top cause of slow pages and unstable Core Web Vitals. We work server-to-server. No snippet to ship, no bundle to grow, no layout shift to explain to sales. Source: web.dev — Third-party JS guidance

Zero JS
No pixels
Faster pages

Ad- & tracker-blocker proof.

Browsers now block cross-site tracking and known trackers by default, breaking pixels and many client-side detections. Our server-side API isn’t affected by ITP/ETP or ad-blockers. Sources: WebKit — Full third-party cookie blocking, Mozilla — Enhanced Tracking Protection

Works behind CDNs
Edge-friendly
S2S reliable

Plans

Start free. Upgrade when volume rises.

Free no card required

  • Rate limit (per minute)10
  • Daily quota500
  • Batch scoringYes
  • SupportCommunity

Pro

  • Rate limit (per minute)3000
  • Daily quota250,000
  • Billing portalSelf-serve
  • SupportPriority
Claim API key now

After checkout, you’ll see your API key (afk_…) once. Send it as X-API-Key with every request.

Easy integration

Use /probe to decide whether the current request looks robotic

Try it via curl:

curl -s -H "X-API-Key: afk_xxx..." https://nobotspls.com/probe

Override any field on the fly

You can override auto-detected values using query parameters.

curl -sG "https://nobotspls.com/probe" \
  --data-urlencode "useragent=Mozilla/5.0 (Macintosh) Safari/605.1.15" \
  --data-urlencode "ip=203.0.xxx.xx" \
  --data-urlencode "headerorder=host, user-agent, accept" \
  -H "X-API-Key: afk_xxx..."

See field definitions below in the HTTP API Doc

The API key is optional but requests are subject to rate limits.
Privacy-first: we don’t store your request payloads or IP addresses. Only aggregate counters for rate-limits/billing are kept so your data stays your data.

HTTP API Doc

Check out the OpenAPI documentation Here.

Authentication

Header Required Description
X-API-Key Yes Your API key (format afk_…).
Content-Type Yes application/json

Examples

Single request (/score):

{
  "useragent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Safari/605.1.15",
  "sec_ch_ua": "\"Chromium\";v=\"123\", \"Not A;Brand\";v=\"99\"",
  "headerorder": "host, user-agent, accept, accept-encoding, accept-language",
  "ip": "127.0.0.1",
  "xforwardedfor": "104.21.32.1",
  "extra_headers": { "cf-connecting-ip": "104.21.32.1" }
}

Batch request (/batch_score):

[
  { "useragent": "Mozilla/5.0 …", "ip": "198.51.100.120", "headerorder": "host, user-agent, accept" },
  { "useragent": "curl/8.4.0",   "ip": "203.0.113.42",   "headerorder": "host, user-agent" }
]

Errors & limits

Status Meaning Notes
200 OK Score computed.
400 Bad Request Invalid or missing fields.
401 Unauthorized Missing/invalid X-API-Key.
429 Rate limited Free: 10 req/min, 500/day. Pro: 3000 req/min, 250k/day.
5xx Server error Retry with backoff.

Notes: probabilities are calibrated to [0,1]; your enforcement can use the provided decision or a custom threshold. Provide as many headers as you naturally have. No client SDK is required.

Quick Start - Probe API

Optionally provide API_KEY with your key (afk_…). Call /probe; we infer headers/IP from the request and return a decision. You can override any field via query params. For more integrations like Wordpress, Nginx or Cloudflare check out the Github Repos.

# pip install requests
import requests

BASE = "http://nobotspls.com"
API_KEY = "afk_xxx..."  # your API key (optional)

# Optional: override auto-detected values via query params
params = {
  # "useragent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Safari/605.1.15",
  # "ip": "x.x.x.x",
  # "headerorder": "host, user-agent, accept"
}

r = requests.get(f"{BASE}/probe",
                 headers={"X-API-Key": API_KEY},
                 params=params,
                 timeout=10)
print(r.status_code, r.json())

# customer_fastapi_example.py
import os
from fastapi import FastAPI, Request, HTTPException, Depends
import httpx

API_BASE = os.getenv("AF_API_BASE", "https://nobotspls.com")
API_KEY  = os.getenv("AF_API_KEY",  "afk_xxx...")

app = FastAPI()

async def bot_guard(request: Request):
    headerorder = ", ".join(
        k.decode("latin1").lower()
        for k, _ in request.scope.get("headers", [])
    )

    h  = request.headers
    ip = ((request.client.host if request.client else "") or "").split(",")[0].strip()

    # Override any values you want the upstream to consider
    params = {
        "useragent":      h.get("user-agent", ""),
        "sec_ch_ua":      h.get("sec-ch-ua", ""),
        "acceptlanguage": h.get("accept-language", ""),
        "acceptencoding": h.get("accept-encoding", ""),
        "accept":         h.get("accept", ""),
        "headerorder":    headerorder,
        "ip":             ip,
    }

    try:
        async with httpx.AsyncClient(timeout=5) as client:
            res = await client.get(
                f"{API_BASE}/probe",
                headers={"X-API-Key": API_KEY},
                params=params,
            )
    except httpx.RequestError as e:
        raise HTTPException(status_code=502, detail=f"Bot check upstream error: {e!s}")

    if res.status_code >= 400:
        raise HTTPException(status_code=502, detail=res.text)

    verdict = (res.json() or {}).get("decision", "allow")
    if verdict == "block":
        raise HTTPException(status_code=403, detail="Blocked by bot detection")

@app.post("/signup", dependencies=[Depends(bot_guard)])
async def signup():
    return {"ok": True}

@app.get("/pricing")
async def pricing(_: None = Depends(bot_guard)):
    return {"plan": "free"}

// Node 18+ (native fetch)
const BASE = "http://nobotspls.com";
const API_KEY = "afk_xxx...";

// Optional: add overrides as query params
const params = new URLSearchParams({
  // useragent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Safari/605.1.15",
  // ip: "x.x.x.x",
  // headerorder: "host, user-agent, accept"
});

fetch(`${BASE}/probe?${params.toString()}`, {
  method: "GET",
  headers: { "X-API-Key": API_KEY }
})
  .then(async (res) => console.log(res.status, await res.json()))
  .catch(console.error);
export default {
  async fetch(request, env) {
    const h = request.headers;
    const ip = (h.get("x-forwarded-for") || h.get("cf-connecting-ip") || "").split(",")[0].trim();

    const params = new URLSearchParams({
      useragent: h.get("user-agent") || "",
      ip
      // Add more overrides if desired, e.g. sec_ch_ua, acceptlanguage, headerorder, ...
    });

    const resp = await fetch("http://nobotspls.com/probe?" + params.toString(), {
      method: "GET",
      headers: { "X-API-Key": env.AFK_API_KEY }
    });

    return new Response(await resp.text(), { status: resp.status });
  }
};