API reference
A small REST API for creating verification sessions and reading their results. JSON in, JSON out.
Base URL and authentication#
https://lkp-verifier.onrender.com/v1Authenticate with a secret key from the console as a bearer token. Keys belong to one app; sessions created with a key are visible only to that app. You can have up to 10 active keys per app and revoke any of them instantly.
Like Stripe, LKP separates Sandbox mode and Live mode using distinct key prefixes:
lkp_sk_test_...: Test keys create sandbox sessions (livemode: false). Sandbox verifications are free, unmetered, and provide a 1-click simulator to test instant pass or fail outcomes without hardware passkeys or real documents.lkp_sk_live_...: Live keys create production sessions (livemode: true) requiring authentic WebAuthn passkey unlocking and Groth16 zero-knowledge proof generation.
curl https://lkp-verifier.onrender.com/v1/verification-sessions \
-H "Authorization: Bearer lkp_sk_test_..."Create a session#
either. Preferred issuance flow when the user does not yet hold an age or identity credential. age_estimation initiates a fast 1 to 2 second selfie age check (wholesale first check rate, sub-cent reuse). full_id requires a passport or national ID card (wholesale first check rate, sub-cent reuse). either gives the user the choice between selfie or ID card.false. When set to true, if a user fails the facial age estimation check for appearing too young, they are offered the option to verify their legal age with an official passport or national ID card instead.ANONYMOUS_LOGIN. In ONE_TIME_ACTION mode each person can complete a given actionId once.poll-2026-q4. Up to 128 characters of letters, digits and . : @ / - _. Defaults to login or default.Requirements
Requirements of the same kind are merged: two AGE_OVER entries keep the stricter one. Exact-match lists are any-of: { "kind": "COUNTRY_EQUALS", "countryIsoNumeric": [250, 826] } accepts France or the UK, and the result does not say which. See Verification types for every option, or list them with GET /v1/verification-types. The response is 201 Created with the session object, in status pending.
Retrieve a session#
Returns the session object. Sessions that belong to another app return 404.
List sessions#
{ "object": "list", "data": [ { "id": "vs_…", "object": "verification_session", … } ] }The session object#
{
"id": "vs_3f9c2a7d0e5b41c8a6f2d9e1b7c4a053",
"object": "verification_session",
"livemode": true,
"appId": "app_8e1f0c2b9d4a7e63",
"status": "verified",
"mode": "ANONYMOUS_LOGIN",
"actionId": "login",
"kycWorkflow": "age_estimation",
"claims": ["Age >= 18 AND Country == 528"],
"requirements": [
{ "kind": "AGE_OVER", "minAge": 18 },
{ "kind": "COUNTRY_EQUALS", "countryIsoNumeric": 528 }
],
"reference": "order_1042",
"redirectUrl": null,
"url": "https://lkp-three.vercel.app/verify/vs_3f9c2a7d0e5b41c8a6f2d9e1b7c4a053",
"result": {
"nullifier": "14898262410530296126834957014925436093817722310495102368117423604186552044231",
"verifiedClaims": ["Age >= 18 AND Country == 528"],
"idToken": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ik…"
},
"failureReason": null,
"attempts": 1,
"verificationMs": 41,
"createdAt": "2026-11-06T15:00:00.000Z",
"expiresAt": "2026-11-06T15:30:00.000Z",
"completedAt": "2026-11-06T15:00:19.000Z"
}false while LKP runs in test mode, where identity checks use sandbox data. Treat such results as test data, never as proof about a real person.pending (waiting for the user), verified, failed, or expired (still pending 30 minutes after creation).verified. nullifier is a decimal string: the anonymous user ID, stable per person and app. idToken is a signed JWT (see ID tokens).Funnel and verification stats#
Track conversion and verification performance across your integration. Analytics endpoints aggregate metrics on the server with zero personal data, zero holder identifiers, and zero IP addresses.
Returns aggregate counts for each step of the user journey:
{
"totals": {
"gate_shown": 1420,
"wallet_created_or_unlocked": 1380,
"age_check_passed": 1290,
"reuse_proof": 1150,
"estimation_vs_id_path": 140
},
"daily": [
{ "date": "2026-11-06", "gate_shown": 95, "wallet_created_or_unlocked": 92, "age_check_passed": 88, "reuse_proof": 80, "estimation_vs_id_path": 8 }
]
}Errors#
Errors use standard HTTP status codes and a JSON body:
{ "error": "Bad Request", "message": "requirements.0.minAge: Number must be greater than or equal to 1" }400: invalid body, conflicting requirements, or a redirect URL that isn't allowed.401: missing, malformed or revoked API key.404: no such session for this app.410: the session is no longer pending (seen by the hosted page, not by your server).429: rate limited. Back off and retry.
Rate limits#
The /v1 endpoints allow 120 requests per minute per client IP. The hosted page has its own, separate limits. If you need more, contact us.