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.
Tweak assumptions to see impact on avoided waste and signal quality.
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.
Block traffic from known data centers, VPNs, open proxies-and the hard part: residential proxy networks used to mimic “real” users.
A purpose-built model scores risk alongside continuously refreshed IP & crawler datasets. You get precision now and it gets smarter over time.
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
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
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
Start free. Upgrade when volume rises.
After checkout, you’ll see your API key (afk_…
) once. Send it as X-API-Key
with every
request.
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
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" \
-H "X-API-Key: afk_xxx..."
See field definitions below in the HTTP API Doc
Check out the OpenAPI documentation Here.
Header | Required | Description |
---|---|---|
X-API-Key |
Yes | Your API key (format afk_… ). |
Content-Type |
Yes | application/json |
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\"",
"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" },
{ "useragent": "curl/8.4.0", "ip": "203.0.113.42" }
]
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.
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",
# ...
}
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):
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", ""),
"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",
// ...
});
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, ...
});
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 });
}
};