Two endpoints: one public-key identify call for clients, one secret-key events call for server-side verification. All requests and responses are JSON. Local dev base URL: http://localhost:8080.
Authentication
Endpoint
Key type
Header
POST /v1/identify
Public (vp_pk_...), safe in browsers
x-api-key: vp_pk_...
GET /v1/events/:requestId
Secret (vp_sk_...), server-side only
x-api-secret: vp_sk_... or Authorization: Bearer vp_sk_...
Key types are enforced: a secret key on /v1/identify or a public key on /v1/events both return 401.
POST /v1/identify
Identifies the device behind a request. The browser SDK calls this for you; you can also call it directly from any HTTP client.
Request body
Field
Type
Description
signals
object, required
The device signal vector: timezone, platform, hardware, fonts, canvas hashes, and so on. See the signal catalog for every field and its weight.
anchors
object, optional
Previously issued visitor IDs read back from client storage: cookieId, localStorageId, cacheId. Any valid anchor short-circuits fingerprint matching.
clientBot
object, optional
Client-side automation pre-checks (webdriver flags, headless markers) collected by the SDK. Merged into the server-side bot verdict.
incognitoHint
boolean, optional
Client-side private-browsing detection result, used as one input to the incognito verdict.
url
string, optional
Page URL where identification ran; shown in the dashboard event stream.
sdkVersion
string, optional
SDK version string for debugging and compatibility tracking.
Response: 200
Field
Type
Description
visitorId
string
Stable visitor identifier, vp_.... The same device resolves to the same ID across visits, subject to the confidence score.
requestId
string
One-time ID for this identification event, vpr_.... Use it with /v1/events to verify server-side.
confidence
number
Match confidence, 0 to 1, computed per request from anchor agreement and signal similarity.
confidenceLevel
"high" | "medium" | "low"
Banded version of confidence for simple threshold logic.
ip
string
Requesting IP address as seen by the API.
geo
object
IP-derived location and network data. See the geo table below.
incognito
boolean
Whether the session appears to be private browsing.
bot
object
Bot detection verdict. See the bot table below.
vpnLikely
boolean
Whether the traffic pattern and network data suggest a VPN.
firstSeenAt
string (ISO 8601)
When this visitor was first identified. Preserved when visitor records are re-linked.
lastSeenAt
string (ISO 8601)
Timestamp of the previous identification of this visitor.
visitCount
number
Total identifications recorded for this visitor.
matchedVia
string[]
How the visitor was recognized on this request: any of cookie, localStorage, cache, fingerprint, or new for a first sighting.
Returns the full identification record for a requestId, exactly as the API computed it. This is the trust boundary: the browser can lie about its own response, but it cannot forge what this endpoint returns.
The complete identify response for that request, the same shape as POST /v1/identify above.
Verification pattern
Have the client forward only the requestId to your backend, fetch the record here, and act on identification.visitorId, bot, and confidence from this response. Treat a mismatch between the claimed and verified visitor ID as a fraud signal in its own right.
Errors
Status
Body
When
401
{ "error", "message" }
Missing key, unknown key, or wrong key type for the endpoint (secret key on identify, public key on events).
404
{ "error", "message" }
/v1/events only: the requestId is unknown.
429
{ "error": "rate_limited", "message", "limit" }
The per-key rate limit was exceeded. The body includes your current limit.
Rate limits
Limits are enforced per API key. When you exceed yours, requests return 429 with the limit echoed in the body. Back off and retry after a short delay. On self-hosted deployments the ceiling is set with the RATE_LIMIT_MAX environment variable; see Self-hosting.
rate-limited.json
{
"error": "rate_limited",
"message": "Rate limit exceeded for this API key.",
"limit": 120
}