Verification Sessions
A verification session represents a single identity verification attempt.
When do I need this?
If you're using the widget with a publishable key and products, sessions are created automatically and the age tier is resolved for you — you don't need this API. Use the Verification Sessions API when you need server-side control over session creation, or want to retrieve, cancel, or list sessions programmatically.
Examples show cURL, Python, Go, and Node.js. You can also use the Node.js SDK as a typed convenience wrapper, or call the API from any language — see HTTP API Overview.
Session statuses
| Status | Description |
|---|---|
created | Session created, waiting for the user to start verification. |
pending | User has opened the verification UI. |
processing | Verification is actively being processed by a method. |
requires_input | A method requires additional user input. |
verified | Verification succeeded. Terminal state. |
failed | All methods were exhausted without success. Terminal state. |
cancelled | Cancelled by your server or the user. Terminal state. |
expired | The 24-hour expiry passed before completion. Terminal state. |
Create a session
/v1/verification_sessions| Parameter | Type | Description |
|---|---|---|
typerequired | "identity" | "age" | "student" | The type of verification to perform. Determines the default method waterfall. |
methods | VerificationMethod[] | Override the default method waterfall. E.g. ["mdl", "facial_age"] to try MDL first then facial age estimation. |
return_url | string | URL to redirect to after successful verification. |
cancel_url | string | URL to redirect to if the user cancels. |
client_reference_id | string | Your internal ID for this user or transaction. Returned in webhook events. |
metadata | object | Up to 50 key-value pairs of arbitrary data. Values must be strings. |
idempotency_key | string | A unique key to prevent duplicate session creation. If a session was already created with this key, the existing session is returned. |
jurisdiction | string | Explicit jurisdiction override (e.g. "US-CA", "US-TX"). If omitted, derived from request geo headers. |
custom_user_message | string | Override the default user-facing message shown in the widget. Max 500 characters. |
email | string | User email for Verified Person lookup. Enables cross-site verification reuse when accept_existing is true. |
phone | string | User phone for Verified Person lookup. Used alongside or instead of email. |
accept_existing | boolean | When true, accepts existing cross-site verifications instead of requiring a new one. Requires email or phone. |
min_strength | string | Minimum credential strength for existing verifications. E.g. "document_capture" to only accept doc capture or stronger. |
max_age | string | Maximum age of existing verification. Format: "<number>d" (e.g. "30d" for 30 days). |
required_methods | string[] | Require ALL of these methods to be previously verified when reusing existing credentials. |
delivery_jurisdiction | string | Optional delivery/shipping jurisdiction (e.g. "US-OR"). When provided, the response includes ip_jurisdiction and jurisdiction_mismatch to flag VPN/geolocation discrepancies. |
curl https://api.stile.dev/v1/verification_sessions \
-H "Authorization: Bearer vk_test_..." \
-H "Content-Type: application/json" \
-d '{
"type": "age",
"use_case": "alcohol_delivery",
"methods": ["mdl", "facial_age"],
"return_url": "https://yourapp.com/done",
"client_reference_id": "user_123"
}'import requests
res = requests.post(
"https://api.stile.dev/v1/verification_sessions",
headers={"Authorization": "Bearer vk_test_..."},
json={
"type": "age",
"use_case": "alcohol_delivery",
"methods": ["mdl", "facial_age"],
"return_url": "https://yourapp.com/done",
"client_reference_id": "user_123",
},
)
session = res.json()body := strings.NewReader(`{
"type": "age",
"use_case": "alcohol_delivery",
"methods": ["mdl", "facial_age"],
"return_url": "https://yourapp.com/done",
"client_reference_id": "user_123"
}`)
req, _ := http.NewRequest("POST", "https://api.stile.dev/v1/verification_sessions", body)
req.Header.Set("Authorization", "Bearer vk_test_...")
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)const session = await stile.verificationSessions.create({
type: "age",
use_case: "alcohol_delivery",
methods: ["mdl", "facial_age"],
return_url: "https://yourapp.com/done",
client_reference_id: "user_123",
});
// Returns:
// {
// id: "vks_abc123",
// object: "verification_session",
// status: "created",
// type: "age",
// client_secret: "eyJ...", ← pass to frontend
// age_tier: "over_21", ← resolved internally from use_case + jurisdiction
// jurisdiction: "US-CA", ← auto-detected
// expires_at: 1741651200,
// livemode: false,
// created: 1741564800,
// ip_jurisdiction: "US-CA", ← only when delivery_jurisdiction is set
// jurisdiction_mismatch: false, ← true if delivery ≠ IP jurisdiction
// }Retrieve a session
/v1/verification_sessions/:idPass expand[]=results to include the full verification results array in the response.
curl "https://api.stile.dev/v1/verification_sessions/vks_abc123?expand[]=results" \
-H "Authorization: Bearer vk_test_..."import requests
res = requests.get(
"https://api.stile.dev/v1/verification_sessions/vks_abc123",
headers={"Authorization": "Bearer vk_test_..."},
params={"expand[]": "results"},
)
session = res.json()req, _ := http.NewRequest("GET", "https://api.stile.dev/v1/verification_sessions/vks_abc123?expand[]=results", nil)
req.Header.Set("Authorization", "Bearer vk_test_...")
res, _ := http.DefaultClient.Do(req)const session = await stile.verificationSessions.retrieve("vks_abc123", {
expand: ["results"],
});
// session.results — array of VerificationResult objectsCancel a session
/v1/verification_sessions/:id/cancelCancels a session that is in created, pending, or requires_input status. Cannot cancel a session that is already processing, verified, failed, or expired.
curl -X POST https://api.stile.dev/v1/verification_sessions/vks_abc123/cancel \
-H "Authorization: Bearer vk_test_..."import requests
res = requests.post(
"https://api.stile.dev/v1/verification_sessions/vks_abc123/cancel",
headers={"Authorization": "Bearer vk_test_..."},
)
session = res.json()req, _ := http.NewRequest("POST", "https://api.stile.dev/v1/verification_sessions/vks_abc123/cancel", nil)
req.Header.Set("Authorization", "Bearer vk_test_...")
res, _ := http.DefaultClient.Do(req)const session = await stile.verificationSessions.cancel("vks_abc123");
// session.status === "cancelled"List sessions
/v1/verification_sessionsReturns a paginated list of sessions for your organization, newest first.
| Parameter | Type | Description |
|---|---|---|
limit | number= 10 | Number of sessions to return. Between 1 and 100. |
starting_after | string | Session ID to start after (cursor-based pagination). Returns sessions created before this ID. |
status | string | Filter by session status (e.g. "verified", "failed", "created"). |
curl "https://api.stile.dev/v1/verification_sessions?limit=20" \
-H "Authorization: Bearer vk_test_..."import requests
res = requests.get(
"https://api.stile.dev/v1/verification_sessions",
headers={"Authorization": "Bearer vk_test_..."},
params={"limit": 20},
)
data = res.json()req, _ := http.NewRequest("GET", "https://api.stile.dev/v1/verification_sessions?limit=20", nil)
req.Header.Set("Authorization", "Bearer vk_test_...")
res, _ := http.DefaultClient.Do(req)const { data, has_more } = await stile.verificationSessions.list({
limit: 20,
});
for (const session of data) {
console.log(session.id, session.status);
}Compliance
To determine the required age tier for a product type, use the Compliance API. It resolves regulatory rules by jurisdiction and handles mixed-cart scenarios automatically. When you pass use_case on the session, this is done for you internally.
Verification methods
Each verification type has a default method waterfall. You can override it by passing a methods array when creating the session.
| Method | Value | Description |
|---|---|---|
| Mobile Driver's License | mdl | ISO 18013-5 mDL via OID4VP. Highest assurance. |
| Mobile Identity | mid | Government-issued digital identity credential. |
| EUDI PID | eudi_pid | European Digital Identity credential. |
| Facial Age Estimation | facial_age | AI age estimate from a selfie. No document required. |
| Selfie Liveness | selfie_liveness | Confirms a real person is present via a live selfie check. |
| Selfie Match | selfie_match | Matches a selfie against a previously verified photo. |
| Self Attestation | self_attestation | User self-declares their age or identity. Lowest assurance. |
| Document Capture | document_capture | Extracts and verifies data from a physical ID document photo. |
| Carrier Lookup | carrier_lookup | Phone carrier verification for age signals. |
| Open Banking | open_banking | Bank account data for identity signals. |
| Parental Consent | parental_consent | Collects consent from a parent or guardian. |
| Student Verification | student | Academic enrollment verification. |
The default waterfall for identity is: mdl → mid → facial_age → carrier_lookup → open_banking.
For age: mdl → mid → facial_age → parental_consent → carrier_lookup → open_banking.
Methods are automatically filtered by jurisdiction — for example, mDL is removed in states that don't recognize digital driver's licenses.