Nexiel Verify runs one identity verification flow: EUDI wallet sessions (OpenID4VP/DCQL/SD-JWT VC) for age verification and AML identity checks.
Nexiel Verify is live in production: compliance.nexiel.io serves /v1/verify/* today and always runs in production mode, so a real request there needs the bearer token described in Authentication and production mode below. Production completion of an aml-identity or age-verificationcheck still fails closed: it depends on the EU's own eIDAS infrastructure publishing PID-Provider trust anchors, which has not happened yet. The requests and responses below are real, executed against a self-run instance with no VERIFY_API_MODE set (sandbox mode, the default), so you can see the exact shape you will integrate against without a wallet or an access token. Against https://compliance.nexiel.io itself, add -H "Authorization: Bearer <access_token>" to every request below, per Authentication and production mode, or it is rejected with a 401.
New to EUDI wallets? Read what an EU Digital Identity Wallet is, and how a session works before this page assumes you already know what OpenID4VP, DCQL, and SD-JWT VC mean.
The session-initiate step below is a plain HTTP request you can call directly. Completing a session requires a wallet-signed presentation, which a bare HTTP client cannot produce on its own. The response shown is the exact shape your server receives once a wallet completes that step.
Nexiel Age's technical core: a DCQL query offering two alternative ways to answer the same age-over-18 question, via credential_sets(a wallet answers exactly one): the EU's own Age Verification (AV) attestation, disclosing an already-minimized native age_over_18 boolean directly, or the PID's birthdateclaim, used solely to compute that same boolean server-side and discarded immediately. Neither path discloses anything else about the holder's identity. This step is a plain curl-able HTTP request:
curl -s -X POST https://compliance.nexiel.io/v1/verify/sessions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <access_token>" \
-d '{"clientId": "docs-example-client", "checkType": "age-verification"}'{
"sessionId": "f1be0a07-e161-4827-a4c3-3337952859ff",
"templateVersion": "age-verification@2",
"authorizationRequest": {
"client_id": "https://verify.nexiel.eu",
"response_type": "vp_token",
"response_mode": "direct_post",
"response_uri": "https://compliance.nexiel.io/v1/verify/sessions/f1be0a07-e161-4827-a4c3-3337952859ff/responses",
"dcql_query": {
"credentials": [
{
"id": "av_age_over_18",
"format": "mso_mdoc",
"meta": {
"doctype_value": "eu.europa.ec.av.1"
},
"claims": [
{
"path": [
"eu.europa.ec.av.1",
"age_over_18"
]
}
]
},
{
"id": "pid_birthdate",
"format": "dc+sd-jwt",
"meta": {
"vct_values": [
"urn:eudi:pid:1"
]
},
"claims": [
{
"path": [
"birthdate"
]
}
]
}
],
"credential_sets": [
{
"options": [
[
"av_age_over_18"
],
[
"pid_birthdate"
]
],
"required": true
}
]
},
"nonce": "-SZgEPglIAiTT0LcV1smKeyCna49ccKyqNdsz98jxj4",
"state": "w3b6d4ohOvuYDe3t50rl7BroLjlBl_c5I1ROHGIyxcU"
},
"authorizationRequestUri": "openid4vp://authorize?client_id=https%3A%2F%2Fverify.nexiel.eu&response_type=vp_token&...(truncated)",
"expiresAt": "2026-07-05T13:09:09.445Z",
"sessionToken": "sGl3q1z8...(truncated, 43 chars)"
}sessionToken is returned exactly once, here. Hold onto it if you want your own frontend to poll GET /v1/verify/sessions/{sessionId} directly (via the X-Nexiel-Verify-Session-Token header) instead of proxying every poll through your backend. It is safe to pass to a browser: it proves nothing about your identity and can only ever read back this one session, never any other session on your account. Session creation always requires your real API key, server-side; only reading a session back afterward has this lighter-weight option.
The wallet resolves authorizationRequestUri and answers exactly one of the two credential_setsoptions: the AV attestation where it holds one (the preferred, more data-minimizing path), or the PID's birthdate claim as a fallback, POSTing the presentation back to response_uri. This API computes age_over_18 from whichever alternative was answered, discarding a disclosed birthdate immediately either way. A wallet that answers with the holder being 18 or older returns:
{
"sessionId": "f1be0a07-e161-4827-a4c3-3337952859ff",
"method": "eudi_wallet",
"booleanProofs": {
"ageOver18": true
},
"amlResult": null,
"pepFlag": null,
"sanctionedFlag": null,
"listsChecked": [],
"humanReviewRequired": false,
"templateVersion": "age-verification@2",
"frozenDecisionArtifact": null
}Only a boolean proof and template version are returned, no birthdate, no name. frozenDecisionArtifact is null here because age verification never runs an AML/PEP screen. That boolean plus the session/client/timestamp/method is all that gets persisted afterward.
Same session flow, checkType: "aml-identity": the wallet discloses given_name, family_name, birthdate, and nationalities, and the holder is screened via the same matching engine Nexiel Screen uses. This example shows a name that matches a sandbox-seeded entity but with a contradicting date of birth, close enough for the fuzzy layers to surface but never auto-cleared or auto-confirmed:
{
"sessionId": "4fc1ad3c-a197-4b09-bc62-68842fc69947",
"method": "eudi_wallet",
"booleanProofs": {
"claimsVerified": true
},
"amlResult": "POSSIBLE_MATCH",
"pepFlag": false,
"sanctionedFlag": true,
"listsChecked": [
"sandbox"
],
"humanReviewRequired": true,
"templateVersion": "aml-identity@1",
"frozenDecisionArtifact": {
"engineVersion": "0.1.0-provisional",
"gatesHash": "212b018655f47f1514c4b404a02e643ef027b5039964d5c68dcdd5ec0dff8f3b",
"ensembleWeightsHash": "4a2723f74864b3371c860bbb93c3d6fbfde7dcc21e99e1242ddad3ff366cf69d",
"listSnapshotIds": []
}
}humanReviewRequired: true is the EU AI Act human-in-the-loop gate: this result is never surfaced to an end user as a final decision. This is a screening result, not a legal determination. See the legal disclaimerfor what Nexiel's outputs do and do not mean.
This wallet-callback reply never includes an evidence pack. The wallet is not your own backend, so it is the wrong channel for handing your account an encrypted copy of what was just disclosed.
GET /v1/verify/sessions/{sessionId}Register a public key first (see setting up evidence pack encryption), and this same decision also produces an encrypted “evidence pack”: the given name, family name, DOB, and nationality just disclosed (the first of the wallet's disclosed nationalities), this same screening decision, the frozen decision artifact, and the wallet's issuer signature validation result. Your own backend collects it with a follow-up read, not the callback reply above:
curl -s https://compliance.nexiel.io/v1/verify/sessions/4fc1ad3c-a197-4b09-bc62-68842fc69947 \
-H "Authorization: Bearer <access_token>"{
"sessionId": "4fc1ad3c-a197-4b09-bc62-68842fc69947",
"clientId": "docs-example-client",
"checkType": "aml-identity",
"templateVersion": "aml-identity@1",
"status": "completed",
"createdAt": "2026-07-05T13:09:09.445Z",
"expiresAt": "2026-07-05T13:24:09.445Z",
"decision": {
"method": "eudi_wallet",
"booleanProofs": {
"claimsVerified": true
},
"amlResult": "POSSIBLE_MATCH",
"pepFlag": false,
"sanctionedFlag": true,
"listsChecked": [
"sandbox"
],
"humanReviewRequired": true,
"frozenDecisionArtifact": {
"engineVersion": "0.1.0-provisional",
"gatesHash": "212b018655f47f1514c4b404a02e643ef027b5039964d5c68dcdd5ec0dff8f3b",
"ensembleWeightsHash": "4a2723f74864b3371c860bbb93c3d6fbfde7dcc21e99e1242ddad3ff366cf69d",
"listSnapshotIds": []
},
"credentialMechanism": "pid_sd_jwt_vc",
"evidencePackCiphertext": "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSJ9..(truncated)",
"decidedAt": "2026-07-05T13:09:12.001Z"
}
}evidencePackCiphertext is delivered exactly once: the first request that returns it consumes that delivery, and every later request, even a retry, gets null there instead. It is also purged automatically after that first successful fetch, or after 7 days if nobody fetches it, whichever comes first. Only your own registered private key can decrypt it, and Nexiel never holds a copy of that key. credentialMechanism is audit-only context (which real mechanism answered the DCQL request: pid_sd_jwt_vc for the PID's birthdate/identity claims, or av_mdoc for the EU Age Verification attestation's native age_over_18boolean). It is never present on the wallet's own synchronous POST .../responses callback reply above, only here and in the audit log.
GET /v1/verify/sessions/{sessionId} collects it. Without a registered key, Nexiel has nothing to encrypt to, so no evidence pack exists for that decision at all. See setting up evidence pack encryption.A self-run sandbox-mode instance needs no API key at all. compliance.nexiel.io itself always runs in production mode, so every /v1/verify route there requires a real OAuth 2.0 client-credentials access token, validated per-request via RFC 7662 introspection (so a revoked key is rejected immediately, even for a still-time-valid token), with a restricted agent-readonly scope available for the Agent Toolkit. A production key can only be minted for a client that has cleared the KYB gate. Every authenticated production request is rate-limited per client and screened for three self-baselined anomaly signals (request volume, geography, and a shift in the mix of checkTypes a client typically sends), tracked independently of Nexiel Screen's own limits for the same client. Only a genuine volume spike ever automatically throttles a client; geography and behavior-shift are logged for an operator to review and never block a request by themselves. A client can also restrict its own key to specific source IPs or domains (off by default; contact us to enable it). A request from outside a configured allowlist returns 403 client_access_restricted.
Once approved, your dashboard's API Keys page gives you a client_id/client_secret pair, shown once. Exchange it for a short-lived access token the normal OAuth2 client-credentials way — this token, not the client secret itself, is what goes in the Authorization header:
curl -s -X POST https://auth.nexiel.io/realms/nexiel/protocol/openid-connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d grant_type=client_credentials \
-d client_id=<client_id> \
-d client_secret=<client_secret> \
| jq -r .access_tokencurl -s -X POST https://compliance.nexiel.io/v1/verify/sessions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <access_token>" \
-d '{"clientId": "docs-example-client", "checkType": "age-verification"}'See the full API reference(generated directly from this service's OpenAPI specification) for every parameter, response schema, and status code.