← Back to NoBotsPls

Getting Started

Everything you need to integrate NoBotsPls bot detection into your stack.

1. Authentication

Send your API key with every request.

HeaderRequiredDescription
X-API-Key Yes Your API key (format afk_…). Get one free at /billing.
Content-Type For POST endpoints application/json

2. Probe API — quickest path

GET /probe auto-detects headers and IP from the incoming request and returns a score + decision. This is the easiest way to integrate — just proxy the request through your backend.

Basic call

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

Override auto-detected values

Pass query parameters to override any field:

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..."

Available override fields

ParameterDescription
useragentUser-Agent string
sec_ch_uaClient Hints UA string
sec_ch_ua_mobileClient Hints mobile flag
acceptlanguageAccept-Language header
acceptencodingAccept-Encoding header
acceptAccept header
ipClient IP address
xforwardedforX-Forwarded-For chain
xrealipX-Real-IP header
viaVia header
dntDo Not Track header
pragmaPragma header
cache_controlCache-Control header
upgrade_insecure_requestsUpgrade-Insecure-Requests header
extra_headersJSON object of additional headers

3. Score API — full control

POST /score lets you send an explicit payload with all fields. Use this when you want full control over what gets scored.

{
  "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" }
}

Response shape

{
  "p_is_invalid": 0.94,
  "p_is_converted": 0.02,
  "decision": "block",
  "rule_hits": ["ua_mismatch", "datacenter_ip"],
  "request_id": "a1b2c3d4-...",
  "ip_enrichment": { "is_datacenter": true, "is_vpn": false, "is_residential_proxy": false },
  "token_attribution_invalid": [ { "token": "sec-ch-ua:missing", "importance": 0.31 }, ... ],
  "neighbors_total_counts": { "inv": 8, "ok": 1 }
}

4. Batch scoring

POST /batch_score accepts an array of score requests (max 500). Same schema as /score, returned as an array.

[
  { "useragent": "Mozilla/5.0 …", "ip": "198.51.100.120" },
  { "useragent": "curl/8.4.0",   "ip": "203.0.113.42" }
]

Also available via the batch UI.

5. Code examples

Using the /probe endpoint. For more integrations (WordPress, Nginx, Cloudflare) see the GitHub repos.

# pip install requests
import requests

BASE = "https://nobotspls.com"
API_KEY = "afk_xxx..."  # your API key

params = {
  # "useragent": "Mozilla/5.0 ...",
  # "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())
# FastAPI middleware example
import os
from fastapi import FastAPI, Request, HTTPException, Depends
import httpx

API_BASE = os.getenv("NOBOTSPLS_API_BASE", "https://nobotspls.com")
API_KEY  = os.getenv("NOBOTSPLS_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()

    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 = "https://nobotspls.com";
const API_KEY = "afk_xxx...";

const params = new URLSearchParams({
  // useragent: "Mozilla/5.0 ...",
  // 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
    });

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

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

6. Errors & rate limits

StatusMeaningNotes
200OKScore computed.
400Bad RequestInvalid or missing fields.
401UnauthorizedMissing/invalid X-API-Key.
429Rate limitedFree: 10 req/min, 500/day. Pro: 3,000 req/min, 250k/day.
5xxServer errorRetry with backoff.

Probabilities are calibrated to [0,1]. Use the provided decision field or apply your own threshold.

7. OpenAPI spec

Full interactive API documentation is available at /docs (Swagger UI, auto-generated by FastAPI).

Privacy-first: we don't store your request payloads or IP addresses. Only aggregate counters for rate-limits/billing are kept — your data stays your data.