Skip to content

Trusted devices

Requires a license with the device-fingerprint feature. See pricing.

This feature watches admin sign-in. It recognizes the device behind each login, scores how risky the login looks, and asks for a second factor when the score is high. It locks out repeated failed attempts, can refuse addresses a reputation provider flags, and keeps a login history admins can review. People mark the browsers they trust, and a trusted browser lowers the score.

Every sign-in gets a score from 0 to 100 and a level:

LevelScoreEffect
low0 to 30Sign-in proceeds.
medium31 to 60A second factor is required.
high61 to 80A second factor is required.
critical81 to 100A second factor is required, and the instance logs a warning.

The score rises for a device the person has not used before, a run of recent failed attempts, many different addresses, a switch between IPv4 and IPv6, sign-in outside the person's three busiest hours or during the unusual-hours window, an address the reputation provider reports as a VPN, and when the person has no trusted device at all. A trusted device lowers it. A flagged address, a Tor exit node and an unreadable login history require a second factor whatever the score.

  1. Install a license that includes device-fingerprint. See licensing and tiers.
  2. Behind a load balancer or reverse proxy, set TRUSTED_PROXIES to its CIDR ranges. Without it every login appears to come from the proxy.
  3. Restart the instance.

People manage their trusted browsers from the account menu in the admin console, under Trusted devices.

You need a token in TOKEN. The quickstart shows how to get one.

  1. Trust the browser or machine you are calling from:

    Terminal window
    curl -X POST http://localhost:3001/api/admin/devices/trust \
    -H "Authorization: Bearer $TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"label": "Work laptop"}'
    { "device_id": "4c80c9ea-ebbe-4074-9073-d7dae73f89e5", "label": "Work laptop" }
  2. List your trusted devices:

    Terminal window
    curl http://localhost:3001/api/admin/devices \
    -H "Authorization: Bearer $TOKEN"
    {
    "data": [
    { "id": "4c80c9ea-ebbe-4074-9073-d7dae73f89e5", "user_id": "3e7cc859-1207-4c7c-9af2-363fa5079e0b", "label": "Work laptop", "last_seen_at": "2026-10-01T09:30:00Z", "created_at": "2026-10-01T09:30:00Z" }
    ],
    "limit": 50,
    "offset": 0,
    "total_count": 1
    }
  3. Read your own login history. Put your user id in USER_ID:

    Terminal window
    curl "http://localhost:3001/api/admin/session-anomaly/history?user_id=$USER_ID&limit=20" \
    -H "Authorization: Bearer $TOKEN"
    {
    "entries": [
    { "id": "e6d012b9-2064-4efc-a5c5-a852ba27bbe7", "user_id": "3e7cc859-1207-4c7c-9af2-363fa5079e0b",
    "ip": "203.0.113.7", "user_agent": "Mozilla/5.0", "success": true,
    "timestamp": "2026-10-01T08:02:10Z", "risk_level": "low", "risk_score": 10,
    "risk_reason": "10 signals: [no trusted devices configured]" }
    ],
    "limit": 20,
    "offset": 0
    }
  4. Forget the device again:

    Terminal window
    curl -X DELETE http://localhost:3001/api/admin/devices/4c80c9ea-ebbe-4074-9073-d7dae73f89e5 \
    -H "Authorization: Bearer $TOKEN"

    The answer is 204.

The guard covers every sign-in route: password login and token, MFA verification, magic link, OAuth, SAML, WebAuthn and SCIM. It counts refused attempts (401 and 403) per tenant, address and account. After brute_force_max_attempts failures within brute_force_window_secs, that pair is blocked for brute_force_block_secs:

{ "error": "too many failed attempts: account temporarily locked", "code": "BRUTE_FORCE_BLOCKED" }

The status is 429.

Set IPREP_PROVIDER=abuseipdb and IPREP_API_KEY to check each sign-in address with AbuseIPDB. Results are cached for an hour. A flagged address is refused with 403:

{ "error": "access denied: IP address has been flagged as abusive", "code": "IP_REPUTATION_BLOCKED" }

abuseipdb without an API key leaves the gate off.

VariableWhat it doesDefault
IPREP_PROVIDERabuseipdb or none. Any other value turns the gate off.none
IPREP_API_KEYThe provider's API key.empty
IPREP_BLOCK_ABUSE_CONFIDENCEBlock at or above this abuse confidence.90
IPREP_BLOCK_TORBlock Tor exit nodes. false does not turn Tor blocking off. Use ip_rep.block_tor below for that.on
IPREP_BLOCK_VPNBlock commercial VPN addresses.false
IPREP_FAIL_CLOSEDRefuse the sign-in when the provider cannot be reached.off, so the sign-in proceeds

Set DEVICE_FINGERPRINT_CONFIG to a JSON object with any of these keys. Invalid JSON is logged and the defaults are kept.

Terminal window
DEVICE_FINGERPRINT_CONFIG='{"brute_force_max_attempts": 10, "ip_rep": {"block_tor": false}}'
KeyMeaningDefault
unusual_hour_start, unusual_hour_endHours (UTC, 0 to 23) that count as unusual.0, 5
brute_force_max_attemptsFailures before a block.5
brute_force_window_secsWindow the failures are counted in.900
brute_force_block_secsHow long a block lasts.1800
brute_force_max_statesAddress and account pairs tracked at once.10000
history_retention_daysAge at which the purge route deletes login history.90
fail_openLet a login through when its history cannot be read.false
ip_rep.block_torBlock Tor exit nodes.true

If you set LYEVE_PLUGINS, include device-fingerprint in it.

An admin who is not a super admin can read only their own history and anomalies.

Device and review routes
MethodPathWhoPurpose
GET/api/admin/devicessigned inYour trusted devices (limit default 50, at most 500, offset).
POST/api/admin/devices/trustsigned inTrust the device making this request. Optional body {"label": "Work laptop"}.
DELETE/api/admin/devices/{id}signed inForget a device. 204.
POST/api/admin/devices/{id}/untrustsigned inSame as the DELETE route.
GET/api/admin/session-anomaly/history?user_id=<id>adminA person's login history, with limit and offset.
GET/api/admin/session-anomaly/anomalies?user_id=<id>adminRisky logins and device changes found in that history.
DELETE/api/admin/session-anomaly/historysuper adminDelete the tenant's history older than history_retention_days.
GET/api/admin/session-anomaly/bruteforce/blockssuper adminActive brute-force blocks in the tenant.
DELETE/api/admin/session-anomaly/bruteforce/blockssuper adminLift a block. Body {"ip": "203.0.113.7", "user_id": "<id>"}. See the caution above.
GET/api/admin/session-anomaly/iprep/cacheadminWhether the reputation gate started, its provider and cache size. With IPREP_PROVIDER=none it reports "enabled": true and checks nothing.
POST/api/admin/session-anomaly/iprep/cache/clearsuper adminClear the reputation cache.

Nobody can read or change another person's devices.

With flows, the device_fingerprint.risk node scores a login from a trigger's payload, so a flow can alert or lock an account on the result. A privacy erasure request removes the addresses, browsers and device fingerprints stored for the person.

StatusMessageCause
429BRUTE_FORCE_BLOCKEDToo many failed sign-ins.
403IP_REPUTATION_BLOCKEDThe sign-in address is flagged.
404The requested endpoint does not exist.The instance started without a license that includes device-fingerprint.
402payment_requiredThe license stopped including device-fingerprint while the instance was running.
Every other error
StatusMessageCause
400user_id query parameter is requiredHistory or anomalies without user_id.
400invalid user_id formatuser_id is not a UUID.
400ip and user_id are requiredUnblock without both.
401unauthorizedA device route without a signed-in user.
403access denied: admins can only view their own login historyAn admin named another person's history.
403access denied: admins can only view their own anomaliesAn admin named another person's anomalies.
404no block found for this ip+user pairNothing to unblock.
404IP reputation not enabledCache clear while the gate could not start.