@nexiel/sdk (TypeScript) and nexiel-sdk(Python) are one client per service, generated request/response types kept fresh against each service's real openapi.json, real typed exceptions instead of raw thrown JSON, and built-in Authorization / Idempotency-Key header handling. Both wrap the same Screen API and Verify API documented elsewhere on this site. Nothing about the underlying routes or response shapes changes, only how much code you write to call them.
Not on npm or PyPI yet. @nexiel/sdk and nexiel-sdk are real, tested code generated from each service's own OpenAPI spec. Install either by cloning the repository and building from source in the meantime.
Both SDKs expose the raw Verify API primitives directly: initiateSession, getSession, listSessions, submitSessionResponse. Every real EUDI wallet integration needs the same thing after calling initiateSession: keep checking getSessionuntil the end user's wallet has responded. Without a helper for that, every integration hand-rolls its own poll loop. waitForSessionCompletion (TypeScript) and wait_for_session_completion (Python) are that loop, built in: they poll getSession on an interval until status leaves 'pending', and return the final session, including its decision once completed.
This is the entire flow, start to finish. Compare it to a hand-rolled integration: you do not write the loop, pick the interval, or reason about the timeout. waitForSessionCompletion owns all three, with sensible defaults (poll every 2 seconds, give up after 2 minutes) you can override.
import { NexielVerifyClient } from '@nexiel/sdk';
const verify = new NexielVerifyClient({ baseUrl: 'https://compliance.nexiel.io' }); // sandbox mode: no apiKey needed
const session = await verify.initiateSession({
clientId: 'your-client-id',
checkType: 'aml-identity',
});
// Present session.authorizationRequest to the end user's wallet (a QR code
// or same-device deep link). See @nexiel/verify-widget for a drop-in UI.
const result = await verify.waitForSessionCompletion(session.sessionId);
if (result.status === 'completed' && result.decision) {
console.log(result.decision.amlResult, result.decision.pepFlag, result.decision.sanctionedFlag);
// The frozen decision artifact a regulator can use to re-derive exactly
// what logic ran, months later.
console.log(result.decision.frozenDecisionArtifact.gatesHash);
}from nexiel_sdk import NexielClient
nexiel = NexielClient(screen_base_url="https://compliance.nexiel.io", verify_base_url="https://compliance.nexiel.io")
session = nexiel.verify.initiate_session(client_id="your-client-id", check_type="aml-identity")
# Present session.authorization_request to the end user's wallet.
result = nexiel.verify.wait_for_session_completion(session.session_id)
if str(result.status) == "completed" and result.decision is not None:
print(result.decision.aml_result, result.decision.pep_flag, result.decision.sanctioned_flag)
print(result.decision.frozen_decision_artifact.gates_hash)If the session is still 'pending' once the timeout elapses, both SDKs raise a dedicated error (NexielTimeoutError) carrying the session id, rather than returning a half-finished result you might mistake for a real outcome. The session itself may still complete later. Catch this, and either poll again or treat it as abandoned, whichever your product needs.
import { NexielTimeoutError } from '@nexiel/sdk';
try {
await verify.waitForSessionCompletion(session.sessionId, { timeoutMs: 30_000 });
} catch (error) {
if (error instanceof NexielTimeoutError) {
console.log(`Session ${error.sessionId} is taking longer than ${error.timeoutMs}ms. Keep the QR code up.`);
} else {
throw error;
}
}Nexiel Age is not a separate engine. It is the same Verify API, the same waitForSessionCompletion helper, and the same session shape as above. The only difference is checkType. Passing 'age-verification' instead of 'aml-identity' requests only the wallet's birthdate claim, for the sole purpose of computing an age_over_18 boolean server-side. That boolean is what you get back; the birthdate itself is discarded immediately and never stored, logged, or returned, and no name or nationality is ever requested. See the Age API docs for the full selective-disclosure walkthrough.
const session = await verify.initiateSession({
clientId: 'your-client-id',
checkType: 'age-verification', // the only change from the AML identity example above
});
const result = await verify.waitForSessionCompletion(session.sessionId);
if (result.status === 'completed' && result.decision) {
console.log('age_over_18:', result.decision.booleanProofs['ageOver18']);
// amlResult, pepFlag, sanctionedFlag, and frozenDecisionArtifact are all
// null here: an age-verification check never runs the sanctions/PEP
// matching engine, so there is nothing to screen and nothing to freeze.
}session = nexiel.verify.initiate_session(client_id="your-client-id", check_type="age-verification")
result = nexiel.verify.wait_for_session_completion(session.session_id)
if str(result.status) == "completed" and result.decision is not None:
print("age_over_18:", result.decision.boolean_proofs["ageOver18"])Every non-2xx response raises a typed subclass of NexielApiError (NexielBadRequestError, NexielUnauthorizedError, NexielForbiddenError, NexielNotFoundError, NexielConflictError, NexielGoneError, NexielRateLimitError, NexielServerError), carrying the real HTTP status, a stable machine-readable code, and the parsed response body, never a raw JSON blob you have to shape-check yourself.
Both SDKs also wrap Nexiel Screen's sanctions/PEP screening. POSSIBLE_MATCH always requires human review. The SDK never auto-clears it, and neither does the API underneath it.
import { NexielClient } from '@nexiel/sdk';
const nexiel = new NexielClient({ screenBaseUrl: 'https://compliance.nexiel.io', verifyBaseUrl: 'https://compliance.nexiel.io' });
const result = await nexiel.screen.screenName({ name: 'Jane Doe' });
console.log(result.classification); // 'CLEAR' | 'POSSIBLE_MATCH' | 'CONFIRMED_MATCH'@nexiel/verify-widget: a drop-in embeddable widget that runs this same initiate/poll flow for you, so you do not have to call the SDK directly at all.Both SDKs regenerate their request/response types directly from each service's openapi.json on every release, so they can never silently drift out of sync with the APIs they wrap. See Verify API reference and Screen API reference for the full route-by-route documentation both SDKs are generated against.