API Documentation
Device.AI provides real-time device intelligence and bot detection through a simple REST API. Get your API key instantly — no signup required — and start detecting bots in under 60 seconds.
https://device.ai#Quickstart
Three steps to protect your site from bots. No account creation, no email, no credit card.
1. Get your API key
curl -X POST https://device.ai/v1/keys \
-H "Content-Type: application/json"
# Response:
# {
# "key": "dv_live_aBcDeFgHiJkLmNoPqRsTuVwXyZ012345",
# "rateLimit": 1000,
# "period": "daily"
# }2. Verify a visitor
curl -X POST https://device.ai/v1/verify \
-H "Content-Type: application/json" \
-d '{
"key": "dv_live_YOUR_KEY",
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)...",
"ip": "203.0.113.42"
}'
# Response:
# {
# "score": 0.92,
# "bot": false,
# "risk": "low",
# "keyInfo": {
# "usage": 1,
# "limit": 1000,
# "period": "daily"
# }
# }3. Block or allow based on score
// In your API route or middleware
const response = await fetch('https://device.ai/v1/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
key: process.env.DEVICE_AI_KEY,
userAgent: req.headers['user-agent'],
ip: req.ip
})
});
const { score, bot, risk } = await response.json();
if (bot || score < 0.3) {
return res.status(403).json({ error: 'bot_detected' });
}
// Continue with normal request handling...#Authentication
All API requests are authenticated using your API key, passed in the request body. Keys are prefixed with dv_live_ for production use.
Important: Keep your API key secure. Do not expose it in client-side code. Always call the Device.AI API from your backend server.
#Generate API Key
/v1/keysGenerate a new API key instantly. No authentication required. Each key includes 1,000 free verifications per day.
Request
No request body required. Just send an empty POST.
curl -X POST https://device.ai/v1/keys \
-H "Content-Type: application/json"Response
{
"key": "dv_live_aBcDeFgHiJkLmNoPqRsTuVwXyZ012345",
"rateLimit": 1000,
"period": "daily",
"created": "2026-04-01T10:00:00.000Z",
"id": 42
}Response Fields
keystringYour API key. Store this securely — it cannot be retrieved again.
rateLimitintegerMaximum verifications allowed per period (default: 1,000).
periodstringRate limit window — currently "daily" (resets at midnight UTC).
createdISO 8601Timestamp when the key was created.
idintegerInternal key identifier.
#Verify Endpoint
/v1/verifyScore a visitor's device for bot likelihood. Returns a trust score from 0.0 (definitely a bot) to 1.0 (definitely human).
Request Body
keystringrequiredYour Device.AI API key.
userAgentstringThe visitor's User-Agent header. Strongly recommended for accurate scoring.
ipstringThe visitor's IP address. Used for reputation lookup and geo-analysis.
curl -X POST https://device.ai/v1/verify \
-H "Content-Type: application/json" \
-d '{
"key": "dv_live_YOUR_KEY",
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
"ip": "203.0.113.42"
}'Response
{
"score": 0.92,
"bot": false,
"risk": "low",
"keyInfo": {
"usage": 47,
"limit": 1000,
"period": "daily"
}
}Response Fields
scorefloatTrust score from 0.0 to 1.0. Higher = more likely human.
botbooleanWhether the visitor is classified as a bot (score < 0.3).
riskstringRisk level: "low" (≥0.7), "medium" (0.3–0.7), or "high" (<0.3).
keyInfo.usageintegerNumber of verifications used in the current period.
keyInfo.limitintegerMaximum verifications allowed per period.
keyInfo.periodstringCurrent rate limit period.
#Shield (WAF Middleware)
/v1/shieldNEWLightweight server-side bot blocking for middleware and edge functions. Unlike /v1/verifywhich uses client-side fingerprinting for deep scoring, Shield is optimized for server-side-only environments where you need fast (<50ms) allow/block decisions based on IP, User-Agent, geo, and referrer signals.
When to use Shield vs Verify: Use /v1/shield in server-side middleware (Next.js middleware, Express, Cloudflare Workers) where you only have request headers. Use /v1/verify when you also have client-side signals from detect.js.
Request Body
keystringrequiredYour Device.AI API key.
ipstringThe visitor's IP address from x-forwarded-for or equivalent.
userAgentstringThe visitor's User-Agent header.
geoobjectGeo information: { country: "US", region: "NY", city: "New York" }
urlstringThe request path being accessed (for path-based rules).
referrerstringThe request referrer (for spam referrer blocking).
rulesobjectOptional per-request rules — see Rules section below.
Rules Object
blockCountriesstring[]ISO country codes to block outright, e.g. ["NG", "CN"]
challengeCountriesstring[]ISO codes to score more aggressively (raises threat score).
blockPathsstring[]URL regex patterns to apply extra protection on, e.g. ["/admin.*", "/api/.*"]
allowBotsbooleanAllow known good bots like Googlebot. Default: true.
strictModebooleanLower blocking threshold for sensitive routes. Default: false.
curl -X POST https://device.ai/v1/shield \
-H "Content-Type: application/json" \
-d '{
"key": "dv_live_YOUR_KEY",
"ip": "203.0.113.42",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)...",
"geo": { "country": "NG" },
"url": "/api/submit",
"referrer": "https://syndicatedsearch.goog",
"rules": {
"blockCountries": ["NG"],
"blockPaths": ["/admin.*"],
"strictMode": true
}
}'Response
{
"action": "BLOCK",
"score": 0.0,
"reason": "blocked_country:NG, bad_referrer",
"signals": ["blocked_country:NG", "bad_referrer"],
"geo": { "country": "NG" },
"cached": false
}Response Fields
actionstring"ALLOW", "BLOCK", or "CHALLENGE". Use this to decide what to do with the request.
scorefloatTrust score from 0.0 to 1.0 (same scale as /v1/verify).
reasonstringHuman-readable explanation of the decision.
signalsstring[]Individual signals that contributed to the score.
geoobjectResolved geo data from the request.
cachedbooleanWhether the result was served from cache.
Signal Types
good_botRecognized search engine or social media crawler
bad_uaUser-Agent matches known bot/scraper patterns
no_ua / short_uaMissing or suspiciously short User-Agent string
blocked_country:XXCountry matched an explicit block rule
challenge_country:XXCountry matched a challenge rule
high_bot_country:XXCountry has high historical bot traffic
bad_referrerReferrer matches known spam/fake referral source
protected_path:patternURL matched a protected path rule
no_ipNo IP address provided (suspicious)
Fail-open by design: If Shield encounters an internal error, it returns action: "ALLOW"to prevent blocking legitimate traffic. Your site's availability always takes priority over bot blocking.
Next.js Middleware Integration
Drop-in middleware that protects your entire site with one file:
import { NextRequest, NextResponse } from 'next/server';
const DEVICE_AI_KEY = process.env.DEVICE_AI_KEY!;
const SHIELD_URL = 'https://device.ai/v1/shield';
export async function middleware(request: NextRequest) {
// Skip static assets
if (request.nextUrl.pathname.startsWith('/_next') ||
request.nextUrl.pathname.startsWith('/favicon')) {
return NextResponse.next();
}
try {
const res = await fetch(SHIELD_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
key: DEVICE_AI_KEY,
ip: request.headers.get('x-forwarded-for')?.split(',')[0]?.trim(),
userAgent: request.headers.get('user-agent') || '',
geo: {
country: request.geo?.country,
region: request.geo?.region,
city: request.geo?.city,
},
url: request.nextUrl.pathname,
referrer: request.headers.get('referer') || '',
rules: {
blockCountries: ['NG'], // Block Nigerian bots
challengeCountries: ['CN'], // Challenge Chinese traffic
blockPaths: ['/admin.*', '/api/sensitive.*'],
strictMode: false,
},
}),
});
const { action } = await res.json();
if (action === 'BLOCK') {
return new NextResponse('Access denied', { status: 403 });
}
} catch {
// Fail open — never block real users due to API errors
}
return NextResponse.next();
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};Cloudflare Worker
export default {
async fetch(request, env) {
const url = new URL(request.url);
// Skip static assets
if (url.pathname.match(/\.(js|css|png|jpg|svg|ico)$/)) {
return fetch(request);
}
try {
const shield = await fetch('https://device.ai/v1/shield', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
key: env.DEVICE_AI_KEY,
ip: request.headers.get('cf-connecting-ip'),
userAgent: request.headers.get('user-agent') || '',
geo: {
country: request.cf?.country,
region: request.cf?.region,
},
url: url.pathname,
referrer: request.headers.get('referer') || '',
}),
});
const { action, reason } = await shield.json();
if (action === 'BLOCK') {
return new Response('Blocked by Device.AI Shield', {
status: 403,
headers: { 'X-Shield-Reason': reason },
});
}
} catch {
// Fail open
}
return fetch(request);
},
};Express.js Middleware
const DEVICE_AI_KEY = process.env.DEVICE_AI_KEY;
async function shieldMiddleware(req, res, next) {
try {
const response = await fetch('https://device.ai/v1/shield', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
key: DEVICE_AI_KEY,
ip: req.ip || req.headers['x-forwarded-for'],
userAgent: req.headers['user-agent'],
url: req.path,
referrer: req.headers.referer || '',
}),
});
const { action } = await response.json();
if (action === 'BLOCK') {
return res.status(403).json({ error: 'blocked_by_shield' });
}
next();
} catch {
next(); // Fail open
}
}
// Protect all routes:
// app.use(shieldMiddleware);
// Protect specific routes:
// app.use('/api', shieldMiddleware);
// app.use('/admin', shieldMiddleware);
module.exports = shieldMiddleware;#Scoring Model
Device.AI analyzes multiple device signals to generate a trust score. The current model evaluates:
User-Agent Analysis
Known bot patterns, browser consistency, header anomalies
Good Bot Allowlist
Googlebot, Bingbot, GPTBot, and 20+ verified crawlers are auto-allowed
UA Length & Structure
Suspiciously short or malformed user agents are penalized
Browser Fingerprint
Missing standard browser identifiers lower the score
Score Ranges
#Rate Limits
Every API key includes a daily verification quota. When you exceed it, requests return a 429 status with an upgrade link.
1,000/day100,000/mo500,000/mo2,000,000/moRate Limit Response
{
"error": "Rate limit exceeded",
"rateLimit": {
"limit": 1000,
"used": 1000
},
"upgradeUrl": "https://device.ai/upgrade?key=dv_live_YOUR_KEY"
}#Error Handling
Device.AI uses standard HTTP status codes. Errors return a JSON body with an error field.
200Verification completed. Check the score.
201API key generated successfully.
400Missing required field (e.g., API key).
403Invalid or deactivated API key.
429Rate limit exceeded. Upgrade or wait for daily reset.
500Something went wrong on our end. Retry with backoff.
#Framework Integrations
Device.AI works with any backend. Here are copy-paste examples for popular frameworks.
Next.js (App Router)
import { NextRequest, NextResponse } from 'next/server';
export async function middleware(request: NextRequest) {
// Skip static assets and API routes
if (request.nextUrl.pathname.startsWith('/_next')) return;
try {
const res = await fetch('https://device.ai/v1/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
key: process.env.DEVICE_AI_KEY,
userAgent: request.headers.get('user-agent') || '',
ip: request.headers.get('x-forwarded-for') || request.ip,
}),
});
const { bot, score } = await res.json();
if (bot) {
return NextResponse.json(
{ error: 'Access denied' },
{ status: 403 }
);
}
} catch {
// Fail open — don't block users if Device.AI is unreachable
}
return NextResponse.next();
}Express.js
const DEVICE_AI_KEY = process.env.DEVICE_AI_KEY;
async function botGuard(req, res, next) {
try {
const response = await fetch('https://device.ai/v1/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
key: DEVICE_AI_KEY,
userAgent: req.headers['user-agent'],
ip: req.ip,
}),
});
const { bot, score } = await response.json();
if (bot) {
return res.status(403).json({ error: 'bot_detected' });
}
// Attach score to request for downstream use
req.deviceScore = score;
next();
} catch (err) {
// Fail open
next();
}
}
// Usage:
// app.use('/api', botGuard);
module.exports = botGuard;Python (Flask)
import os, requests
from functools import wraps
from flask import request, jsonify
DEVICE_AI_KEY = os.environ['DEVICE_AI_KEY']
def bot_guard(f):
@wraps(f)
def decorated(*args, **kwargs):
try:
resp = requests.post('https://device.ai/v1/verify', json={
'key': DEVICE_AI_KEY,
'userAgent': request.headers.get('User-Agent', ''),
'ip': request.remote_addr,
}, timeout=2)
data = resp.json()
if data.get('bot'):
return jsonify({'error': 'bot_detected'}), 403
except Exception:
pass # Fail open
return f(*args, **kwargs)
return decorated
# Usage:
# @app.route('/submit')
# @bot_guard
# def submit():
# ...#SDKs & Libraries
Official SDKs are coming soon. In the meantime, the REST API works with any HTTP client.
Node.js
Coming soon
Python
Coming soon
Go
Planned
PHP
Planned
Ready to get started?
Get your free API key and start detecting bots in under a minute.
Get Free API Key →