Verified Person
Look up a reusable credential previously created by your organization and avoid unnecessary verification for returning users.
The Verified Person API answers one organization-scoped question: has this user already completed a verification with us that still meets our requirements? Look up a credential by email or phone from your backend, or let session creation perform the lookup and ownership-proof flow for you.
Two ways to use it:
- Manual lookup — call
POST /v1/verified_person/lookupfrom your backend and branch on the result yourself. - Session-level reuse — pass
accept_existing(plusemailorphone, and an optionalmin_strength) when creating a session and let Stile run the lookup and reuse flow for you. See Combine with session-level reuse below.
Examples show cURL, Python, Go, and Node.js. The Node.js examples use direct HTTPS unless they are explicitly labeled private preview.
Look up a verified person
/v1/verified_person/lookupCall this from your backend with your secret key. If an already-authenticated user has a credential at the required strength, you can reuse it without opening the widget.
Authenticate the user first
An email address or phone number is an identifier, not proof of ownership. Only branch directly on this lookup for a user your application has already authenticated. For user-supplied identifiers, use session-level reuse so Stile can require the workflow's OTP gate before accepting a credential.
| Parameter | Type | Description |
|---|---|---|
email | string | User email. At least one of email or phone is required. |
phone | string | User phone number. At least one of email or phone is required. |
methods | string[] | Require ALL of these methods to have been previously verified for a match. |
min_strength | string | Minimum credential strength to accept. E.g. "document_capture" to only accept doc capture or stronger (MDL, MID, EUDI PID). See the strength ranking below. |
max_age | string | Maximum age of the verification in days. E.g. "30" for the last 30 days. |
curl -X POST https://api.stile.id/v1/verified_person/lookup \
-H "Authorization: Bearer stile_sk_..." \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"min_strength": "document_capture"
}'import requests
res = requests.post(
"https://api.stile.id/v1/verified_person/lookup",
headers={"Authorization": "Bearer stile_sk_..."},
json={
"email": "user@example.com",
"min_strength": "document_capture",
},
)
result = res.json()
if result["verified"]:
print(result["verified_person_id"])body := strings.NewReader(`{"email":"user@example.com","min_strength":"document_capture"}`)
req, _ := http.NewRequest("POST", "https://api.stile.id/v1/verified_person/lookup", body)
req.Header.Set("Authorization", "Bearer stile_sk_...")
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)const result = await stile.verifiedPersons.lookup({
email: "user@example.com",
min_strength: "document_capture",
max_age: "30",
});
if (result.verified) {
console.log(result.verified_person_id);
console.log(result.credentials);
// [{ method: "MDL", strength: "MDL", verified_at: "...", expires_at: "..." }]
}Response
{
"object": "verified_person_lookup",
"verified": true,
"verified_person_id": "vp_abc123",
"credentials": [
{
"method": "MDL",
"strength": "MDL",
"verified_at": "2025-03-01T12:00:00.000Z",
"expires_at": "2026-03-01T12:00:00.000Z"
}
]
}| Field | Type | Description |
|---|---|---|
object | string | Always "verified_person_lookup". |
verified | boolean | Whether a matching credential exists that satisfies your filters. |
verified_person_id | string | Opaque identifier for the verified person (vp_...), or null if no match. |
credentials | array | Matching credentials: method, strength, verified_at, expires_at. |
Response values are UPPERCASE
Credential method and strength values in the response are UPPERCASE (e.g. "MDL"), while
request parameters like min_strength use lowercase (e.g. "document_capture"). Normalize
accordingly when comparing.
No match found
When no matching credential exists, verified is false and credentials is empty:
{
"object": "verified_person_lookup",
"verified": false,
"verified_person_id": null,
"credentials": []
}Treat this as a first-time user: create a verification session and run the normal flow.
Credential strength
When using min_strength, credentials are ranked from weakest to strongest:
| Rank | Strength | Method |
|---|---|---|
| 1 | self_attestation | User declaration |
| 2 | facial_age | AI age estimation |
| 3 | carrier_lookup | Mobile carrier verification |
| 4 | open_banking | Bank account verification |
| 5 | document_capture | Physical ID scan + OCR |
| 6 | mdl | Mobile Driver's License |
| 7 | mid | Mobile ID |
| 8 | eudi_pid | EU Digital Identity |
A credential at a given strength satisfies any request at that level or below. The table is a compatibility taxonomy and includes reserved method values; it does not mean every row can be configured in a new workflow. For example, a document_capture credential (rank 5) satisfies a min_strength: "facial_age" lookup (rank 2), even though facial_age is not currently a runnable workflow method. The reverse is never true — a weaker credential cannot satisfy a stronger requirement, and the user must complete a step-up verification.
Methods not listed above map onto this ranking: selfie_match and selfie_liveness count at document_capture strength; student and parental_consent count at self_attestation strength.
Combine with session-level reuse
You don't have to orchestrate reuse yourself. Pass the reuse parameters on POST /v1/verification_sessions and Stile runs the lookup as part of the session:
| Parameter | Effect |
|---|---|
email / phone | Identifies the user for the Verified Person lookup. |
accept_existing | When true, accepts an existing credential instead of requiring a new verification. |
min_strength | Minimum credential strength to accept, using the ranking above. |
max_age | Maximum age of the existing verification. Format "30d" — note the d suffix on sessions. |
required_methods | Require ALL of these methods to have been previously verified before reusing. |
Two max_age formats
The lookup endpoint takes max_age as a number of days ("30"); session creation takes a
duration string ("30d"). Don't swap them.
When a same-organization credential matches, the widget can skip the camera flow. If the workflow requires it, the user first proves ownership of the email with a one-time code. The session's requires_email_otp field tells you when this gate applies. The OTP proves control of the email address only; the age or identity proof always comes from the underlying credential.
Repeated accept_existing attempts for the same email are rate-limited to 10 per 15-minute window. The eleventh attempt returns 429 accept_existing_rate_limited. See Error Handling for the full code list.
For the end-to-end returning-user model — VP tokens, email/phone lookup with OTP, and full verification as the fallback — read the Returning Users guide.
Next steps
Returning Users
The three-tier reuse flow: VP tokens, email lookup + OTP, and full verification.
Verification Sessions
Create sessions with accept_existing, min_strength, and max_age.
Trust Reuse
Cross-operator reuse, consent, and the trust_reuse_grant webhook events.
Node.js private preview
Typed verifiedPersons.lookup() and the rest of the API surface.