PlatformXeDocs
Get API Key

Device Registry

Per-tenant device fingerprint registry with four built-in signals and IP-intelligence-derived impossible-travel detection.

The device registry observes device fingerprints across users and time, surfacing four signals you can fold into your rule conditions:

SignalFires when
newDeviceForSubjectThis is the first time the fingerprint has been observed paired with this subject.
deviceSharedAcrossSubjectsThe fingerprint has been observed with ≥ 3 distinct subjects.
impossibleTravelThe fingerprint was last seen in a different country within the implausible-travel window (1h).
deviceRecentlyFirstSeenThe fingerprint is < 24h old.

You compute the fingerprint hash client-side. PlatformXe never sees the raw signal set — only the hash you POST.

Two ways to use it

1. Auto-integration with /decide. When context.deviceFingerprint is present in a /v1/fraud/decide call, the engine automatically registers the observation and injects the signals into the rule evaluation context as device.*. You can write rule conditions like:

{
  "all": [
    { "device.newDeviceForSubject": { "equals": true } },
    { "amount.value": { "gt": 50000 } }
  ]
}

This is the recommended path — one round-trip per decision, full audit trail.

2. Standalone observation. Call POST /v1/fraud/devices/seen directly when you want to register a session-start observation independent of any decision (e.g. on login).

Endpoint

POST /api/v1/fraud/devices/seen

PropertyValue
Scopefraud:decide
Plan gateDetection Pack addon
Rate limit10,000 requests / hour per API key
IdempotentYes (Idempotency-Key header honoured)

Request body

FieldTypeRequiredDescription
fingerprintstringYesTenant-computed hash of the device fingerprint (≤ 256 chars).
subjectIdstringYesThe subject (user) observed using this device.
ipstringNoThe client IP. Used for IP-intelligence-derived signals (impossibleTravel, ipIntel.*).
geostringNoISO 3166-1 alpha-2 country override. Takes precedence over IP intel's resolved country when present.
contextobjectNoFree-form context echoed back; rules can reference these fields under context.*.

Response

{
  "success": true,
  "data": {
    "fingerprintId": "dfp_2j7yhq8wcs9...",
    "fingerprint": "fp_abc123...",
    "signals": {
      "newDeviceForSubject": true,
      "distinctSubjectsSeen": 1,
      "deviceSharedAcrossSubjects": false,
      "impossibleTravel": false,
      "deviceRecentlyFirstSeen": true,
      "tenantTrustScore": null,
      "firstSeenAt": "2026-05-03T09:14:21.412Z",
      "lastSeenAt": "2026-05-03T09:14:21.412Z"
    },
    "ipIntel": {
      "status": "available",
      "cached": false,
      "lookup": {
        "country": "NG",
        "region": "Lagos",
        "asn": "AS37075",
        "isVpn": false,
        "isDatacenter": false,
        "provider": "primary"
      }
    }
  }
}

When IP intelligence is unavailable (no IP supplied, provider unconfigured, vendor outage), ipIntel carries { "status": "unavailable", "reason": "..." } instead. impossibleTravel then stays false rather than firing on partial data.

Field reference

PathTypeNotes
signals.newDeviceForSubjectbooleanFires on the first observation of (fingerprint, subjectId).
signals.distinctSubjectsSeenintegerNumber of distinct subjects observed on this fingerprint, capped at 1,000 stored history.
signals.deviceSharedAcrossSubjectsbooleandistinctSubjectsSeen >= 3.
signals.impossibleTravelbooleanCross-country move within ≤ 1h of the last observation, when both geos are known.
signals.deviceRecentlyFirstSeenbooleanfirstSeenAt is < 24h old.
signals.tenantTrustScoreinteger | nullTenant-managed; lets ops mark known-good devices.
signals.firstSeenAtstringISO-8601 of the first observation.
signals.lastSeenAtstringISO-8601 of the most recent observation, post-upsert.
ipIntel.lookup.countrystring | nullISO 3166-1 alpha-2 from the IP intel provider.
ipIntel.lookup.asnstring | nullAutonomous system, e.g. AS37075.
ipIntel.lookup.isVpn / isDatacenterboolean | nullProvider-supplied.

Rule examples

Block when a fresh device is sharing across users:

{
  "name": "fresh-device-shared",
  "weight": 70,
  "appliesTo": { "actions": ["transfer", "login"] },
  "condition": {
    "all": [
      { "device.deviceRecentlyFirstSeen": { "equals": true } },
      { "device.deviceSharedAcrossSubjects": { "equals": true } }
    ]
  }
}

Step up auth on impossible travel:

{
  "name": "impossible-travel-step-up",
  "weight": 50,
  "verdictOverride": "step_up",
  "appliesTo": { "actions": ["transfer"] },
  "condition": {
    "device.impossibleTravel": { "equals": true }
  }
}

Soft review for VPN traffic above a threshold:

{
  "name": "vpn-large-amount-review",
  "weight": 25,
  "appliesTo": { "actions": ["transfer"] },
  "condition": {
    "all": [
      { "ipIntel.isVpn": { "equals": true } },
      { "amount.value": { "gt": 100000 } }
    ]
  }
}

Privacy

  • Raw IPs never persist. The ip_geo_cache is keyed on a SHA-256 hash of the IP.
  • Fingerprints are tenant-computed hashes; PlatformXe does not derive them and never sees the underlying signal set.
  • The subjects array on each fingerprint row is capped at 1,000 entries (oldest dropped); geos capped at 50.
  • IP intelligence cache TTL is 24h; cron data-retention evicts stale rows.

Errors

HTTPCodeCause
400BAD_REQUESTMissing or oversized fingerprint, missing subjectId, non-string ip / geo.
401UNAUTHORIZEDMissing or invalid API key.
402DETECTION_PACK_REQUIREDDetection Pack addon not enabled.
403FORBIDDENAPI key has no organisation context, or wrong scope.
429RATE_LIMITED10,000/hr ceiling exceeded.
500INTERNAL_ERRORUnexpected service failure.