DevRoute SDK
One script tag. Instant device intelligence — eSIM support, fraud signals, network type, and smart conversion routing for every visitor.
DevRoute works entirely client-side. You add a <script> tag,
call DevRoute.init(), and receive a structured result object with
everything you need to route, personalize, or protect your users.
No build step. No dependencies. No npm.
Just a script tag. Works on any website — vanilla HTML, React, Vue, WordPress, Webflow. If it runs JavaScript in a browser, DevRoute works.
Quick Start
Three steps to get your first device result.
Sign up at devroute.app/register. Your key is available immediately on the dashboard.
Paste this in the <head> or just before </body> of your page.
DevRoute.init()
Pass your API key and an onResult callback. That's it.
<!-- Step 2: Add the SDK --> <script src="https://www.devroute.app/sdk/v3/devroute.min.js"></script> <!-- Step 3: Initialize --> <script> DevRoute.init({ apiKey: 'esim_live_YOUR_API_KEY', onResult: function(result) { console.log('Device:', result.device.brand, result.device.model); console.log('eSIM supported:', result.esim?.supported); } }); </script>
Installation
CDN (recommended)
Always points to the latest v3 release. Cached globally via Cloudflare CDN.
<script src="https://www.devroute.app/sdk/v3/devroute.min.js"></script>
Pinned version
Use a specific version if you need reproducible builds.
<script src="https://www.devroute.app/sdk/v3.0.0/devroute.min.js"></script>
The SDK is a single file with zero dependencies and no external requests
beyond the DevRoute API. It works offline (returns cached data from
sessionStorage on repeat calls within the same browser tab).
DevRoute.init(options)
The single entry point. Call it once per page load — usually right after the script tag.
Results are cached in sessionStorage so repeat calls within the same tab
return instantly without an extra API request.
DevRoute.init({ // Required apiKey: 'esim_live_YOUR_API_KEY', // Called with the result object when detection succeeds onResult: function(result) { // result contains device, eSIM, network, and risk data }, // Called if the API request fails (optional) onError: function(err) { console.warn('DevRoute error:', err.message); } });
Options
| Option | Type | Required | Description |
|---|---|---|---|
apiKey |
string | Required | Your DevRoute API key. Get it from the dashboard. Format: esim_live_... |
onResult |
function | Optional | Callback invoked with the result object on success. Also called immediately with cached data if available. |
onError |
function | Optional | Callback invoked if the API request fails. Receives an Error object. |
apiEndpoint |
string | Optional | Override the API endpoint. Defaults to the DevRoute edge worker at https://api.devroute.app/detect. Only change this if instructed by support. |
mode |
string | Optional | 'full' (default) or 'light'. Use 'light' to get a reduced result object optimized for quick eSIM + routing decisions. See Light mode. |
onRule |
function | Optional | Callback invoked when custom business rules match. Receives an array of matched rules: [{ label, action, type, value }]. See Rules. |
Response Object
The result object passed to onResult. The response is
structured in grouped blocks. Available fields depend on your plan —
blocks from higher plans are null on lower plans.
{
// ── Device — Free+ ──────────────────────────────────────────────────
device: {
id: "sha256_device_hash", // unique device fingerprint
brand: "Samsung",
model: "Galaxy S24",
type: "Galaxy S24 Ultra 5G",
confidence: 0.92,
detectionMethod: "server-index-lookup",
os: {
name: "Android",
version: "14"
},
screenResolution: "1080x2340",
pixelRatio: 3,
timezone: "America/New_York",
language: "en-US",
family: "Galaxy S24 Series",
hash: "sha256..."
},
// ── eSIM — Starter+ ─────────────────────────────────────────────────
esim: {
supported: true,
dualEsim: false,
conversionStrategy: "guided_install",
recommendedFlow: "step_by_step",
osReady: true,
minOs: "9.0",
transferSupported: false
},
// ── Network — Growth+ ───────────────────────────────────────────────
network: {
type: "4g",
ip: "203.0.113.42",
isp: "Comcast Cable",
vpn: false,
webrtcLeak: false,
asn: 7922,
httpProtocol: "HTTP/2",
tlsVersion: "TLSv1.3",
velocity: {
flagged: false,
count: 0
}
},
// ── Hardware — Growth+ ──────────────────────────────────────────────
hardware: {
gpu: "Adreno 750",
ram: 8,
cores: 8
},
// ── Security — Enterprise+ ──────────────────────────────────────────
security: {
privateMode: null,
emulator: false,
bot: false,
botSubtype: null,
webrtcRealIp: null,
riskScore: 8,
threatScore: 0,
conflicts: [],
identityInconsistent: false,
consistency: {
score: 1.0,
reasons: []
}
},
// ── Geolocation — Enterprise+ ───────────────────────────────────────
geolocation: {
country: "US",
city: "New York",
region: "New York",
continent: "NA",
postalCode: "10001",
isEUCountry: false,
latitude: 40.71,
longitude: -74.00,
timezoneMismatch: false
},
// ── Intelligence — Starter+ ─────────────────────────────────────────
intelligence: {
decisions: { // 5 layers — depth varies by plan },
profile: { // user segment — Growth+ },
scores: { // risk/conversion/experience — Growth+ },
narrative: { // headlines and recommendations }
},
// ── Rules — Starter+ ────────────────────────────────────────────────
rules: [
{
name: "US Premium Flow",
label: "us_premium",
type: "country",
value: "US",
action: "allow",
priority: 10,
tags: ["geo"],
matchedOn: ["country"]
}
],
// ── Access — Enterprise+ ────────────────────────────────────────────
access: {
outcome: "allow",
source: "system",
reason: "low_fraud_risk",
confidence: 0.85
}
}
Field reference
| Field | Type | Description | Plan |
|---|---|---|---|
device.id | string | SHA-256 device fingerprint (stable across sessions) | Free |
device.brand | string | Device manufacturer | Free |
device.model | string | Device model name | Free |
device.type | string | Full device type string | Free |
device.confidence | number | Detection confidence 0.0–1.0 | Free |
device.detectionMethod | string | How the device was identified | Free |
device.os | object | { name, version } — OS detected | Free |
device.family | string | Device family / product line | Free |
device.hash | string | Stable device fingerprint for cross-session tracking | Free |
esim.supported | boolean | Whether the device supports eSIM | Starter |
esim.dualEsim | boolean | Two concurrent eSIMs supported | Starter |
esim.conversionStrategy | string | Recommended install strategy | Starter |
esim.recommendedFlow | string | Recommended UI flow | Starter |
esim.osReady | boolean | OS version supports eSIM installation | Starter |
esim.minOs | string | Minimum OS version for eSIM | Starter |
esim.transferSupported | boolean | eSIM device-to-device transfer | Starter |
intelligence.decisions.* | object | Five decisions as { value, reason } | Starter |
intelligence.narrative | object | Headlines and recommendations | Starter |
rules | array | Matched custom rules — fires onRule callback | Starter |
intelligence.profile | object | User segment, confidence, signals | Growth |
intelligence.authenticity | object | Authenticity score (0–100) + verdict | Growth |
intelligence.scores | object | risk / conversion / experience (0–100) | Growth |
network.type | string | "2g" / "3g" / "4g" / "5g" / "wifi" | Growth |
network.ip | string | Client IP address | Growth |
network.isp | string | ISP name (from Cloudflare edge) | Growth |
network.vpn | boolean | VPN or datacenter connection | Growth |
network.webrtcLeak | boolean | WebRTC leaked real IP | Growth |
network.asn | number | Autonomous system number | Growth |
network.httpProtocol | string | HTTP version ("HTTP/2" / "HTTP/3") | Growth |
network.tlsVersion | string | TLS version | Growth |
network.velocity | object | { flagged, count } — request velocity (Growth+) | Growth |
hardware.gpu | string | GPU renderer string | Growth |
hardware.ram | number | Device RAM in GB | Growth |
hardware.cores | number | CPU core count | Growth |
security.emulator | boolean | Emulator or automation tool | Enterprise |
security.bot | boolean | Headless browser or scraper | Enterprise |
security.riskScore | number | Fraud risk score 0–100 | Enterprise |
security.privateMode | boolean | Browser private / incognito mode | Enterprise |
security.webrtcRealIp | string | Real IP leaked behind VPN | Enterprise |
security.threatScore | number | IP threat score from Proxycheck.io | Enterprise |
security.conflicts | array | Cross-signal contradictions | Enterprise |
security.consistency | object | { score, reasons } — identity consistency | Enterprise |
geolocation.country | string | ISO 3166-1 alpha-2 | Enterprise |
geolocation.city | string | City name | Enterprise |
geolocation.region | string | Region / state | Enterprise |
geolocation.latitude | number | GPS latitude from Cloudflare edge | Enterprise |
geolocation.longitude | number | GPS longitude | Enterprise |
geolocation.isEUCountry | boolean | True if EU country | Enterprise |
geolocation.timezoneMismatch | boolean | Browser TZ ≠ IP TZ — VPN signal | Enterprise |
access.outcome | string | "allow" / "block" / "challenge" | Enterprise |
access.reason | string | Reason for access decision | Enterprise |
access.confidence | number | Confidence in access decision | Enterprise |
Conversion strategies
The esim.conversionStrategy field tells you how to best route the user. Use it to show the right flow without asking the user to self-identify their device.
| conversionStrategy | recommendedFlow | Meaning | Recommended action |
|---|---|---|---|
"direct_install" | "auto_install" | iOS 16+ or Google Pixel — carrier tap-through available | Show "Activate eSIM" button; deep-link to carrier activation URL |
"guided_install" | "step_by_step" | Samsung or Android 12+ — OS-native eSIM wizard available | Open step-by-step modal linking to Settings > Mobile > Add eSIM |
"qr_install" | "scan_qr" | eSIM supported, no native deep-link flow | Display QR code with scan instructions |
"manual_install" | "manual_setup" | Low confidence detection, cannot verify device | Show activation code entry form as fallback |
"unsupported_device" | "show_unsupported" | Device confirmed as eSIM-incapable | Show upgrade prompt or physical SIM flow |
Detection methods
The detectionMethod field indicates how the device was identified, which reflects detection confidence:
| Value | Confidence | Description |
|---|---|---|
"server-iphone-direct" | High | Matched via screen resolution + DPR matrix (iPhone/iPad) |
"server-multi-signal" | High | Matched via UA + resolution + DPR + OS + touch scoring |
"server-index-lookup" | Medium | Matched via Android model code index (42,000+ codes) |
"server-ua-extract" | Medium | Extracted from UA string with brand inference |
"gpu_cpu_ram_inference" | Low–Medium | Inferred from GPU + CPU family + RAM/screen signals |
Light Mode
Starter
Use mode: 'light' to get a minimal response object optimized for quick eSIM
routing decisions without the full device intelligence payload.
DevRoute.init({ apiKey: 'esim_live_YOUR_KEY', mode: 'light', // ← minimal response onResult: function(result) { // result is a grouped object, no flat fields console.log(result.device?.brand); // "Samsung" console.log(result.esim?.supported); // true console.log(result.network?.type); // "4g" console.log(result.rules); // matched rules array console.log(result.access?.outcome); // "allow" | "block" | "flag" } });
Light mode structure
The response is grouped into nested objects — no flat fields. Each group is populated only for the plan level it belongs to:
| Key | Content | Plan |
|---|---|---|
device | { id, brand, model, type } | Free |
esim | { supported, dualEsim, strategy, osReady } | Starter |
intelligence | { decisions, narrative } | Starter |
rules | [{ label, action, type, value }] | Starter |
network | { type, vpn, isp } | Growth |
hardware | { gpu, ram, cores } | Growth |
geolocation | { country, city } | Enterprise |
security | { bot, emulator, riskScore } | Enterprise |
access | { outcome, reason, confidence } | Enterprise |
Cached results. Light mode results are also cached in sessionStorage.
Subsequent calls in the same tab return instantly without an API request.
Rules — onRule Callback
Starter
When a custom business rule matches on the server, the SDK fires the onRule callback with an array of all matched rules. If multiple rules match, you receive them all in priority order.
DevRoute.init({ apiKey: 'esim_live_YOUR_KEY', onResult: function(result) { // handle device intelligence }, onRule: function(rules) { // rules = [{ label, action, type, value }, ...] rules.forEach(function(rule) { if (rule.action === 'block') { showBlockedMessage(rule.label); } else if (rule.action === 'flag') { logSuspicious(rule.label, rule.type); } else if (rule.action === 'allow') { enablePremiumFlow(rule.value); } }); } });
Rule object fields
| Field | Type | Description |
|---|---|---|
label | string | Programmatic identifier (e.g. "us_premium", "eu_strict"). Use this for logic, not type. |
action | string | "allow" | "block" | "flag" |
type | string | Rule condition type — for logging/analytics |
value | mixed | The value that triggered the rule (e.g. "US" for country match) |
Convention: Use rule.label for programmatic logic and
rule.type for analytics/debugging. Don't rely on rule.value type —
it may be string, number, or boolean depending on the rule.
Helper Methods
Utilities you can call after DevRoute.init() has run:
// Get the last matched rule (null if none) var rule = DevRoute.getRule(); // → { label: "us_premium", action: "allow", type: "country", value: "US" } // Get the full cached result object var cached = DevRoute.getLastResult(); // → full result object (with rules array, etc.) // SDK version console.log(DevRoute.version); // "3.0.0"
| Method | Returns | Description |
|---|---|---|
DevRoute.getRule() | object | null | Last matched rule from the current session, or null |
DevRoute.getLastResult() | object | null | Full cached result from sessionStorage |
DevRoute.version | string | Current SDK semver ("3.0.0") |
Intelligence Engine
Available on Starter and above.
The engine runs five decision layers on every detection and returns structured outputs
with a value and a human-readable reason.
Decisions — intelligence.decisions
Each field returns { value: string, reason: string }. Use value to drive logic and reason to display context or log decisions.
var d = result.intelligence.decisions; // Always use .value to drive your logic console.log(d.esim_flow.value); // "auto_install" console.log(d.esim_flow.reason); // "iOS 17 with confirmed eSIM — use Quick Transfer" console.log(d.friction.value); // "none" | "soft" | "hard" console.log(d.security_gate.value); // "none" | "review" | "block" console.log(d.ui_mode.value); // "streamlined" | "standard" | "cautious" console.log(d.priority.value); // "high" | "medium" | "low"
| Decision | Values | Description |
|---|---|---|
esim_flow | auto_install / step_by_step / scan_qr / manual_setup / show_unsupported | Which eSIM installation flow to trigger |
friction | none / soft / hard | Whether to add verification steps (soft = captcha, hard = SMS OTP) |
security_gate | none / review / block | Whether to allow, flag for review, or block the session |
ui_mode | streamlined / standard / cautious | How much to simplify or guard your UI for this session |
priority | high / medium / low | Conversion priority — use for A/B testing and upsell targeting |
Profile & scores — Growth
Growth plan adds user segment classification and three conversion scores:
var intel = result.intelligence; // User segment intel.profile.segment; // "genuine_user" | "corporate_user" | "privacy_user" // | "traveler" | "developer" | "fraudulent_actor" // | "automated_actor" | "tor_user" | "unclassified" intel.profile.confidence; // 0.0–1.0 segment confidence intel.profile.signals; // ["esim_capable", "residential_isp", ...] // Authenticity intel.authenticity.score; // 0–100 (higher = more authentic) intel.authenticity.verdict; // "authentic" | "suspicious" | "likely_bot" // Scores (0–100) intel.scores.risk; // fraud risk (lower = safer) intel.scores.conversion; // likelihood to convert (higher = better) intel.scores.experience; // UX quality score — use to gate premium features
Error Handling
If the API call fails — network timeout, invalid API key, or plan limit reached —
the onError callback is called. The SDK never throws; all errors
are caught internally.
DevRoute.init({ apiKey: 'esim_live_YOUR_KEY', onResult: function(result) { // happy path }, onError: function(err) { // err.message — human-readable error // Common: "HTTP 401" (bad key), "HTTP 429" (limit reached), network errors console.warn('[DevRoute]', err.message); } });
| HTTP Status | Meaning | What to do |
|---|---|---|
401 | Invalid API key | Check your key in the dashboard. Ensure the request comes from a whitelisted domain. |
429 | Monthly detection limit reached | Upgrade your plan or wait for the next billing cycle. |
500 | Detection failed | Rare. Contact support if it persists. |
Domain locking. API keys can be restricted to specific domains. If you see a 401 on a new domain, add it to the allowed domains list in API Keys settings.
Example — eSIM Routing
Show the right onboarding flow based on real device eSIM support.
DevRoute.init({ apiKey: 'esim_live_YOUR_KEY', onResult: function(result) { var flow = result.esim.conversionStrategy; if (flow === 'direct_install') { // iOS 16+ / Pixel — tap-through carrier activation, no QR needed showActivateButton(carrierDeepLinkUrl); } else if (flow === 'guided_install') { // Samsung / Android 12+ — OS-native Settings wizard showGuidedModal(result.brand, result.OS.version); } else if (flow === 'qr_install') { // eSIM supported, show QR code for profile download showQRCode(); } else if (flow === 'manual_install') { // Low-confidence detection — show activation code entry showActivationCodeForm(); } else if (flow === 'unsupported_device') { // Device can't use eSIM — offer physical SIM or upgrade showPhysicalSimFallback(); } // Optional: use intelligence decisions for UX fine-tuning var d = result.intelligence?.decisions; if (d?.ui_mode?.value === 'streamlined') collapseSecondaryOptions(); if (d?.priority?.value === 'high') showPremiumPlanBanner(); } });
Example — Fraud Detection
Block or flag sessions with high risk signals before checkout. Use intelligence.decisions (Starter+) for decision logic, and risk_score / bot (Enterprise+) for numerical thresholds.
DevRoute.init({ apiKey: 'esim_live_YOUR_KEY', onResult: function(result) { var gate = result.intelligence?.decisions?.security_gate?.value; var friction = result.intelligence?.decisions?.friction?.value; if (gate === 'block') { blockSession(); // high-confidence fraud } else if (gate === 'review' || friction === 'hard') { showSmsVerification(); // suspicious — step-up auth } else if (friction === 'soft') { showCaptcha(); // mild friction only } else { enableCheckout(); // clean session } } });
DevRoute.init({ apiKey: 'esim_live_YOUR_KEY', onResult: function(result) { var riskScore = result.risk_score; // 0–100 var isBot = result.bot; var isVpn = result.vpn; // Growth+ var conflicts = result.signal_conflicts; // array of contradiction strings if (isBot || riskScore > 80) { blockSession(); } else if (isVpn || riskScore > 50 || conflicts.length > 1) { triggerStepUpVerification(); } else { enableCheckout(); } } });
Example — Geo Pricing
Display localized prices based on country without a separate geolocation API call.
var REGIONAL_PRICES = { 'US': { amount: '$4.99', currency: 'USD' }, 'GB': { amount: '£3.99', currency: 'GBP' }, 'DE': { amount: '€4.49', currency: 'EUR' }, 'IN': { amount: '₹99', currency: 'INR' }, }; DevRoute.init({ apiKey: 'esim_live_YOUR_KEY', onResult: function(result) { var country = result.geolocation?.country; var price = REGIONAL_PRICES[country] || REGIONAL_PRICES['US']; document.getElementById('price-display').textContent = price.amount; } });
Plan Limits
| Plan | Monthly detections | Price |
|---|---|---|
| Free | 1,000 | $0 |
| Starter | 25,000 | $39/mo |
| Growth | 100,000 | $129/mo |
| Enterprise | 500,000 | $399/mo |
Limits reset on the 1st of each month. When a limit is reached, the API returns HTTP 429 and detection stops until the next cycle or you upgrade.
Fields by Plan
Fields from higher plans return null on lower plans — no errors, just null values.
| Plan | Available fields |
|---|---|
| Free | id, brand, model, type, confidence, detectionMethod |
| Starter | Free + esim_supported, dual_esim, conversionStrategy, recommendedFlow, esim_osready, family, GPU, networkType, device_tier, device_hash, intelligence.decisions.*, intelligence.narrative.headline, rule_matched, rules_triggered |
| Growth | Starter + IP, isp, vpn, ram, cores, asn, httpProtocol, intelligence.profile, intelligence.authenticity, intelligence.scores |
| Enterprise | Growth + emulator, bot, risk_score, signal_conflicts, timezone_mismatch, country, city, access.* |