Returning User Verification
Reuse a user's prior verification within your organization using VP tokens or an organization-scoped lookup with OTP.
Stile can reuse a user's prior verification within your organization instead of forcing a new camera or ID flow on every visit. This guide covers the same-operator three-tier model and the strength, age-tier, and expiry rules that decide when a user must verify again. For reuse between different operators, read Trust Reuse.
The three-tier model
When a user arrives, the system checks three tiers in order. The first tier that succeeds is used — no unnecessary steps.
| Tier | User experience | When it's used |
|---|---|---|
| Tier 1 | Instant — no user interaction | User has a valid VP token in localStorage for this origin. |
| Tier 2 | Email + OTP code | User has verified with your organization but doesn't have a valid local token. |
| Tier 3 | Full camera + ID flow | First-time user, or existing credentials are expired or insufficient. |
Tier 1: VP tokens
A VP token is a signed JWT stored in the browser's localStorage, scoped to the current origin. It is issued automatically after a successful verification and contains:
- A verified person ID (opaque identifier)
- The credential method and age tier
- An expiry timestamp
- A signature from Stile's servers
The lifecycle:
- User completes a full verification (Tier 3) or the OTP reuse flow (Tier 2).
- Stile issues a VP token and the widget stores it in
localStorage. - On the next visit, the widget finds the token and sends it to Stile for validation.
- If valid, the user passes instantly — no camera, no OTP, no friction.
If you orchestrate this yourself, pass the token as vp_token when creating a session. A malformed, expired, or revoked token returns 400 vp_token_invalid — treat that as "no token" and fall through to Tier 2.
Server-side validation is required
The VP token is a convenience for fast client-side checks, but your server must always validate the token by checking the actual credentials in the database — not just the JWT claims. A tampered token will fail server-side validation.
Tier 2: Organization-scoped lookup with OTP
localStorage is scoped per origin, so a token can be unavailable after the user changes browser, device, or origin. Stile can then look up a credential previously created by your organization and require an email OTP before reusing it:
User arrives without a token
A returning user arrives without a valid VP token for the current origin, so Tier 1 falls through without interrupting the page.
User enters their email
The widget collects the address (or your backend passes email on session creation).
Stile finds an existing credential
The credential was created by a previous verification in your organization and is linked to that email.
User completes an OTP challenge
A one-time code proves they own the email address — and nothing more (see OTP is not proof of age).
A new VP token is issued for this origin
No camera, no ID scan. The next visit passes at Tier 1.
Same operator only
The Verified Person lookup and accept_existing flow are scoped to your organization. They do not
read another operator's credentials. Cross-operator reuse uses Trust Reuse,
which requires user consent plus operator and workflow opt-in.
Check for an existing credential server-side
For a user your application has already authenticated, your backend can ask whether your organization holds a credential that meets the required assurance level:
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"
}'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: "..." }]
}See the Verified Person API for the full parameter and response reference.
Do not treat an email address as authentication
Only use a direct lookup after your application has authenticated the user or independently proved ownership of the email or phone number. For user-supplied identifiers, prefer session-level reuse so the workflow's OTP gate can run before a credential is accepted.
Reuse at session creation
You don't have to orchestrate the lookup yourself. Pass the reuse parameters on POST /v1/verification_sessions and Stile runs the flow as part of the session:
| Parameter | Effect |
|---|---|
email / phone | Identifies the user for the credential lookup. |
accept_existing | When true, accepts an existing credential instead of requiring a new verification. |
min_strength | Minimum credential strength to accept (see the ranking). |
max_age | Maximum age of the existing verification, as a duration string — e.g. "30d". |
required_methods | Require ALL of these methods to have been previously verified before reusing. |
If the workflow requires ownership proof, the session's requires_email_otp field is true and the user must complete the OTP gate before reuse. Otherwise requests fail with 400 email_not_verified or 400 otp_not_proven. When no reusable credential matches your filters, session creation returns 400 no_matching_vp. Repeated accept_existing attempts for the same email are limited to 10 per 15-minute window; the eleventh returns 429 accept_existing_rate_limited.
Email binding
VP tokens are bound to the email address used during verification. This prevents token sharing:
- If user A verifies with
alice@example.com, their VP token is bound to that email. - If user B tries to use the same device, a different email triggers re-verification.
- Changing the email on an existing session forces a full Tier 3 verification.
Credential strength ranking
Not all verification methods carry the same weight. Stile ranks credential methods by strength:
| Rank | Method | Description |
|---|---|---|
| 1 | self_attestation | User self-declares their age (weakest). |
| 2 | facial_age | AI-based age estimation from a selfie. |
| 3 | carrier_lookup | Mobile carrier age check. |
| 4 | open_banking | Age derived from bank account data. |
| 5 | document_capture | Government-issued ID scan + selfie match. |
| 6 | mdl | Mobile driver's license (ISO 18013-5). |
| 7 | mid | Mobile identity document. |
| 8 | eudi_pid | EU Digital Identity wallet credential (strongest). |
This is a compatibility taxonomy and includes reserved values; it is not a list of methods you can publish today. mid, eudi_pid, facial_age, carrier_lookup, open_banking, student, and parental_consent are not currently runnable workflow steps. Methods not listed map onto this ranking: available selfie_match and selfie_liveness count at document_capture strength; reserved student and parental_consent values count at self_attestation strength.
A stronger credential always satisfies a weaker requirement: a user verified with document_capture (rank 5) satisfies a request for self_attestation (rank 1) without re-verifying. The reverse is never true — a self_attestation credential cannot satisfy a document_capture requirement, and the user must complete a step-up verification.
Age tier compatibility
Age tiers follow a strict hierarchy:
min_age_21 --> satisfies min_age_18, min_age_16, and min_age_13
min_age_18 --> satisfies min_age_16 and min_age_13
min_age_16 --> satisfies min_age_13
min_age_13 --> satisfies min_age_13 onlyA credential proving min_age_21 automatically satisfies any lower requirement. A weaker tier (e.g. min_age_16) can never satisfy a stronger one (e.g. min_age_21) — that, too, routes the user to a step-up verification.
Credential expiry
Credentials expire based on jurisdiction compliance rules. The default expiry is 365 days from the date of verification. When a credential expires:
- Tier 1 (VP token) stops working — the token is rejected during validation.
- Tier 2 (email lookup) finds the credential but sees it's expired.
- The user is routed to Tier 3 for a full re-verification.
Jurisdiction-specific expiry
Some jurisdictions require shorter credential lifetimes. Stile automatically applies the correct expiry based on the jurisdiction where the verification was performed.
You can also enforce a stricter recency bar than the credential's own expiry — pass max_age at lookup or session creation to refuse credentials older than your policy allows.
Step-up verification
A valid, non-expired credential can still be insufficient if the new request requires a stronger method or a higher age tier.
Example: A user verified with self_attestation (rank 1) visits a site that requires document_capture (rank 5). Their existing credential doesn't meet the requirement, so they complete a new verification with the stronger method.
After the step-up, the new (stronger) credential replaces the old one. The user now holds a document_capture credential that satisfies both document_capture and self_attestation requirements going forward.
OTP is not proof of age
A common misconception: completing an OTP challenge does not prove anything about the user's age or identity.
- OTP proves: "This person controls this email address."
- OTP enables: Reuse of an existing, previously-verified credential.
- OTP does not prove: Age, identity, or anything else.
The actual proof always comes from the underlying verification credential, such as a verified ID scan or mobile driver's license. The OTP is a gate that ensures only the rightful owner of the email can access the credential linked to it.
Next steps
Verified Person API
Full reference for the lookup endpoint, filters, and response shape.
Verification Sessions
Create sessions with accept_existing, min_strength, max_age, and vp_token.
Trust Reuse
Cross-operator reuse with explicit user consent and grant webhooks.
Testing & Sandbox
skip_verification produces a real VP token — test returning-user flows end to end.