---
title: "Score a device fingerprint"
method: POST
path: "/v1/score/device"
tags: ["Device"]
---

# Score a device fingerprint

`POST /v1/score/device`

## Request

- Base URL: `https://www.layercall.com`
- URL: `https://www.layercall.com/v1/score/device`
- Auth: one of:
  - API key in header `x-api-key`
  - HTTP bearer

Reputation and bot probability for a browser fingerprint produced by https://www.layercall.com/fp.js.

This is the signal that survives an attacker rotating IPs and email addresses: a new VPN exit and a fresh disposable inbox are free, a new machine is not. The `device_id` is hashed in the browser and again before storage, so the stored value cannot be reversed to a device.

POST rather than GET because a device id in a query string ends up in access logs and Referer headers.

## Query parameters

- `strictness` 'lenient' | 'balanced' | 'strict'

## Request body

- object
  - `device_id` string, required: Hex fingerprint from LayerCall.fingerprint().
  - `ip` string: Visitor IP. Optional — enables the timezone-vs-geolocation check.
  - `signals` object: Optional browser characteristics from fp.js. Used for bot scoring only, never persisted.
    - `user_agent` string
    - `timezone` string
    - `timezone_offset` integer
    - `screen` string
    - `hardware_concurrency` integer
    - `languages` string
    - `webgl_renderer` string
  - `automation` object: Optional automation markers from fp.js.
    - `webdriver` boolean: navigator.webdriver was true.
    - `automation_globals` boolean: Selenium/Puppeteer/Playwright globals present.
    - `headless_ua` boolean
    - `zero_screen` boolean
    - `no_plugins` boolean
    - `no_languages` boolean

## Response `200`

Device score

- DeviceScore
  - `device_id` string, required
  - `risk_score` integer, required: 0 = clean, 100 = certain fraud.
  - `verdict` 'allow' | 'review' | 'block', required: Recommended action. Thresholds move with `strictness`.
  - `bot_probability` integer, required: Reported separately from risk: plenty of automated traffic is legitimate.
  - `signals` object, required
    - `is_bot` boolean: bot_probability >= 60.
    - `is_automated` boolean
    - `is_headless` boolean
    - `timezone_mismatch` boolean, nullable: Browser timezone country differs from the IP's. Silent (false) when either is genuinely unknown; null when the country could NOT be resolved — a GeoIP outage — so no comparison was made.
    - `repeat_device` boolean: Seen before across the LayerCall network.
  - `first_seen` string, date-time, nullable
  - `times_seen` integer
  - `abuse_reports` integer
  - `request_id` string
  - `processing_time_sec` number
  - `billable_lookups` integer

## Other responses

- `400`: Missing or malformed input.
- `401`: Missing, invalid, or revoked API key.
- `429`: Monthly quota or spend cap reached, or the 600 requests-per-minute rate limit exceeded.

---

[API](https://skmtc.dev/layercall/apis/layercall-api.md) · [All operations](https://skmtc.dev/layercall/apis/layercall-api/llms.txt) · [OpenAPI document](https://skmtc.dev/layercall/apis/layercall-api/revisions/b1258307dc91?raw)
