stile
HTTP API

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

StatusDescription
createdSession created, waiting for the user to start verification.
pendingUser has opened the verification UI.
processingVerification is actively being processed by a method.
requires_inputA method requires additional user input.
verifiedVerification succeeded. Terminal state.
failedAll methods were exhausted without success. Terminal state.
cancelledCancelled by your server or the user. Terminal state.
expiredThe 24-hour expiry passed before completion. Terminal state.

Create a session

POST/v1/verification_sessions
ParameterTypeDescription
typerequired"identity" | "age" | "student"The type of verification to perform. Determines the default method waterfall.
methodsVerificationMethod[]Override the default method waterfall. E.g. ["mdl", "facial_age"] to try MDL first then facial age estimation.
return_urlstringURL to redirect to after successful verification.
cancel_urlstringURL to redirect to if the user cancels.
client_reference_idstringYour internal ID for this user or transaction. Returned in webhook events.
metadataobjectUp to 50 key-value pairs of arbitrary data. Values must be strings.
idempotency_keystringA unique key to prevent duplicate session creation. If a session was already created with this key, the existing session is returned.
jurisdictionstringExplicit jurisdiction override (e.g. "US-CA", "US-TX"). If omitted, derived from request geo headers.
custom_user_messagestringOverride the default user-facing message shown in the widget. Max 500 characters.
emailstringUser email for Verified Person lookup. Enables cross-site verification reuse when accept_existing is true.
phonestringUser phone for Verified Person lookup. Used alongside or instead of email.
accept_existingbooleanWhen true, accepts existing cross-site verifications instead of requiring a new one. Requires email or phone.
min_strengthstringMinimum credential strength for existing verifications. E.g. "document_capture" to only accept doc capture or stronger.
max_agestringMaximum age of existing verification. Format: "<number>d" (e.g. "30d" for 30 days).
required_methodsstring[]Require ALL of these methods to be previously verified when reusing existing credentials.
delivery_jurisdictionstringOptional 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

GET/v1/verification_sessions/:id

Pass 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 objects

Cancel a session

POST/v1/verification_sessions/:id/cancel

Cancels 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

GET/v1/verification_sessions

Returns a paginated list of sessions for your organization, newest first.

ParameterTypeDescription
limitnumber= 10Number of sessions to return. Between 1 and 100.
starting_afterstringSession ID to start after (cursor-based pagination). Returns sessions created before this ID.
statusstringFilter 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.

MethodValueDescription
Mobile Driver's LicensemdlISO 18013-5 mDL via OID4VP. Highest assurance.
Mobile IdentitymidGovernment-issued digital identity credential.
EUDI PIDeudi_pidEuropean Digital Identity credential.
Facial Age Estimationfacial_ageAI age estimate from a selfie. No document required.
Selfie Livenessselfie_livenessConfirms a real person is present via a live selfie check.
Selfie Matchselfie_matchMatches a selfie against a previously verified photo.
Self Attestationself_attestationUser self-declares their age or identity. Lowest assurance.
Document Capturedocument_captureExtracts and verifies data from a physical ID document photo.
Carrier Lookupcarrier_lookupPhone carrier verification for age signals.
Open Bankingopen_bankingBank account data for identity signals.
Parental Consentparental_consentCollects consent from a parent or guardian.
Student VerificationstudentAcademic 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.

On this page