Bot check
Scores an IP and User-Agent pair. A risk score above 50 sets is_bot. The IP half is deterministic: a merged range table over published datacenter CIDRs, with no heuristics.
Endpoint
1 credit per callPOST https://conureapi.com/v1/bot-check
Request
| Field | Type | Required | Description |
|---|---|---|---|
ip |
string | Required | IPv4 address of the client. |
user_agent |
string | Optional | Raw User-Agent header. Absent or empty is itself a signal. |
headers |
object | Optional | Request headers, up to 64 entries. Missing browser headers add risk. |
Response
{
"is_bot": true,
"risk_score": 100,
"reasons": [
"datacenter_ip",
"missing_sec_ch_ua",
"missing_accept_language",
"missing_sec_fetch_site",
"known_bot_user_agent"
]
}Code examples
curl -X POST https://conureapi.com/v1/bot-check \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"ip":"52.1.2.3","user_agent":"curl/8.4.0"}'
const response = await fetch("https://conureapi.com/v1/bot-check", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({"ip":"52.1.2.3","user_agent":"curl/8.4.0"}),
signal: AbortSignal.timeout(5000),
});
if (!response.ok) throw new Error("conure: " + response.status);
console.log(await response.json());
import requests
HEADERS = {"Authorization": "Bearer YOUR_API_KEY"}
response = requests.post(
"https://conureapi.com/v1/bot-check",
headers=HEADERS,
json={"ip": "52.1.2.3", "user_agent": "curl/8.4.0"},
timeout=5,
)
response.raise_for_status()
print(response.json())
package main
import (
"encoding/json"
"fmt"
"net/http"
"time"
"strings"
)
func main() {
client := &http.Client{Timeout: 5 * time.Second}
payload := strings.NewReader(`{"ip":"52.1.2.3","user_agent":"curl/8.4.0"}`)
req, _ := http.NewRequest("POST", "https://conureapi.com/v1/bot-check", payload)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer YOUR_API_KEY")
resp, err := client.Do(req)
if err != nil {
fmt.Println("conure unavailable, allowing request:", err)
return
}
defer resp.Body.Close()
var verdict map[string]any
json.NewDecoder(resp.Body).Decode(&verdict)
fmt.Println(verdict)
}
$curl = curl_init("https://conureapi.com/v1/bot-check");
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => '{"ip":"52.1.2.3","user_agent":"curl/8.4.0"}',
CURLOPT_HTTPHEADER => [
"Authorization: Bearer YOUR_API_KEY",
"Content-Type: application/json",
],
]);
$body = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
$verdict = $status === 200 ? json_decode($body, true) : null;
var_dump($verdict);
require "net/http"
require "json"
uri = URI("https://conureapi.com/v1/bot-check")
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer YOUR_API_KEY"
request["Content-Type"] = "application/json"
request.body = { ip: "52.1.2.3", user_agent: "curl/8.4.0" }.to_json
response = Net::HTTP.start(uri.hostname, uri.port,
use_ssl: uri.scheme == "https",
open_timeout: 5, read_timeout: 5) do |http|
http.request(request)
end
puts JSON.parse(response.body)
Try it
Runs against the live API from your browser. Your key stays in this tab and is sent nowhere but this endpoint.
Pricing
One credit per call. Your own 4xx and our 5xx are refunded, so you only pay for answers.
Full pricing is on your billing page.
Rate limits
120 requests per minute per API key. Exceeding it returns
429 with a Retry-After header, and costs no credits.
Rate limiting fails open: if the limiter itself is unavailable your request is
served rather than rejected.
Need a higher limit? See errors and limits for how the limiter behaves under load.