npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@smileid/usesmileid-nodejs

v12.0.0

Published

Official Smile ID server-side SDK for JavaScript/TypeScript — V3 APIs.

Downloads

83

Readme

@smileid/usesmileid-nodejs

npm version CI status license

Official Smile ID server-side SDK for JavaScript/TypeScript — V3 APIs.

This project is under active development and is not yet published to npm. The package name and API surface may change before the first release.

Requirements

Node.js 18 or later. The SDK uses the built-in global fetch and has no runtime dependencies. Development and CI use the version pinned in .nvmrc.

Install

npm install @smileid/usesmileid-nodejs

Create a client

Construct a client with your partner id and API key. The SDK handles authentication for you: it fetches an internal token, caches it until just before expiry, and refreshes it once on a 401. You never handle tokens yourself.

import { SmileID } from '@smileid/usesmileid-nodejs';

const smile = new SmileID({
  partnerId: '1234',
  apiKey: process.env.SMILE_API_KEY!,
  environment: 'sandbox', // the default
  defaultCallbackUrl: 'https://app.example.com/smile/callback',
});

Partner ids are displayed zero-padded (for example 002) but must be passed without leading zeros (2).

Environments and base URL

The client targets the sandbox by default. Set environment: 'production' to go live. Only sandbox and production are accepted; anything else is rejected at construction.

| Environment | Base URL | | ------------ | ----------------------------------- | | sandbox | https://testapi.smileidentity.com | | production | https://api.smileidentity.com |

Any other host needs an explicit baseUrl, which wins over environment:

const smile = new SmileID({
  partnerId: '2',
  apiKey: process.env.SMILE_API_KEY!,
  baseUrl: process.env.SMILE_BASE_URL ?? 'https://your-environment.example.com',
});

HTTPS requirements

The SDK talks to Smile ID over HTTPS only:

  • baseUrl must be an absolute https:// URL with no query string or fragment. There is no option to allow plain HTTP.
  • defaultCallbackUrl and every per-request callbackUrl must be https:// URLs. A non-HTTPS callback raises ValidationError before any request is sent.

Both are checked at client construction; per-request callback URLs are checked again before each send.

Other options

| Option | Default | Purpose | | -------------------- | ---------- | ------------------------------------------------------------- | | defaultCallbackUrl | unset | Used when a call omits callbackUrl | | timeout | 30000 ms | Per-request total timeout | | maxRetries | 2 | Retries for idempotent operations only | | fetch | global | Injectable fetch implementation for testing or proxies |

Shared inputs

Every verification call takes a consent block and userDetails. Build consent with the Consent.granted helper. userDetails needs at least one of email or phoneNumber — the SDK checks this before sending.

import { Consent } from '@smileid/usesmileid-nodejs';

const consent = Consent.granted({
  grantedAt: new Date(),
  noticeLanguage: 'EN',
  noticePrivacyPolicyUrl: 'https://example.com/privacy',
});

const userDetails = {
  givenNames: 'Amina Fatou',
  lastName: 'Clearwater',
  email: '[email protected]',
};

Non-production environments match test identities on given names, last name and email. An unrecognised identity resolves to block.

Image inputs (selfieImage, livenessImages, document, comparisonImage) accept a file path, a Buffer, a Blob, or a readable stream. Wrap one in { data, filename, contentType } to override the filename or content type.

Every method takes an optional final options argument with timeout, callbackUrl, and signal (an AbortSignal).

Methods

Enhanced KYC

const accepted = await smile.enhancedKyc.verify({
  country: 'NG',
  idType: 'NIN',
  idNumber: '12345678901',
  userDetails,
  consent,
  userId: 'user_01h8x9y2z3a4b5c6d7e8f9g0h1',
});
accepted.jobId;      // "job_..."
accepted.isAccepted; // true

Document verification

const accepted = await smile.documents.verify({
  country: 'NG',
  selfieImage: './selfie.jpg',
  livenessImages: ['./live1.jpg', './live2.jpg', './live3.jpg', './live4.jpg', './live5.jpg', './live6.jpg'],
  document: './passport.jpg',
  userDetails,
  consent,
});

Enhanced document verification

Same as document verification, but idType is required.

const accepted = await smile.documents.verifyEnhanced({
  country: 'NG',
  idType: 'PASSPORT',
  selfieImage: './selfie.jpg',
  livenessImages: ['./live1.jpg', './live2.jpg', './live3.jpg', './live4.jpg', './live5.jpg', './live6.jpg'],
  document: './passport.jpg',
  userDetails,
  consent,
});

Biometric KYC

const accepted = await smile.biometricKyc.verify({
  country: 'NG',
  idType: 'NIN',
  idNumber: '12345678901',
  selfieImage: './selfie.jpg',
  livenessImages: ['./live1.jpg', './live2.jpg', './live3.jpg', './live4.jpg', './live5.jpg', './live6.jpg'],
  userDetails,
  consent,
});

Biometric enrollment

const accepted = await smile.biometric.enroll({
  selfieImage: './selfie.jpg',
  livenessImages: ['./live1.jpg', './live2.jpg', './live3.jpg', './live4.jpg', './live5.jpg', './live6.jpg'],
  userDetails,
  consent,
  userId: 'user_01h8x9y2z3a4b5c6d7e8f9g0h1',
});

Biometric authentication

userId is required and must match an enrolled user. Images are required unless useEnrolledImage is true.

const accepted = await smile.biometric.authenticate({
  userId: 'user_01h8x9y2z3a4b5c6d7e8f9g0h1',
  selfieImage: './selfie.jpg',
  livenessImages: ['./live1.jpg', './live2.jpg', './live3.jpg', './live4.jpg', './live5.jpg', './live6.jpg'],
  userDetails,
  consent,
});

Selfie comparison

const accepted = await smile.biometric.compare({
  selfieImage: './selfie.jpg',
  comparisonImage: './id-photo.jpg',
  comparisonImageType: 'ID_PHOTO', // DOCUMENT | ID_PHOTO | PORTRAIT
  userDetails,
  consent,
});

Check a verification

retrieve never throws on an unknown job: a 404 comes back as a JobStatus with status: "not_found" so polling can treat it as pending.

status is processing while the job runs, not_found for an unknown job, and otherwise the decision itself: clear, block, attention or error. message is a human-readable note, "Job completed" on a finished job — the decision is never in the message.

const status = await smile.verifications.retrieve('job_01h8x9y2z3a4b5c6d7e8f9g0h1');
status.status;       // e.g. "clear"
status.isComplete;   // true on any decision, i.e. not processing and not not_found
status.isProcessing; // true while running
status.message;      // e.g. "Job completed"

Wait for completion

Polls while the job is processing (and, by default, while it is not_found), then returns the status carrying the decision. Throws TimeoutError when the deadline passes. Options: interval (default 2000 ms), timeout (default 60000 ms), and treatNotFoundAsPending (default true).

const status = await smile.verifications.waitUntilComplete('job_01h8x9y2z3a4b5c6d7e8f9g0h1', {
  interval: 2000,
  timeout: 60000,
});

Replay a callback

Only completed verifications can be replayed; a replay of a job that is still processing throws ConflictError.

const accepted = await smile.verifications.replay('job_01h8x9y2z3a4b5c6d7e8f9g0h1', {
  callbackUrl: 'https://app.example.com/smile/callback',
});

Report fraud

reason is required when flagging fraud; notes is required when clearing it or when the reason is OTHER.

const accepted = await smile.users.reportFraud('user_01h8x9y2z3a4b5c6d7e8f9g0h1', {
  isFraud: true,
  reason: 'ACCOUNT_TAKEOVER',
  reportedBy: '[email protected]',
});

The flagFraud and clearFraud wrappers set isFraud for you:

await smile.users.flagFraud('user_01h8x9y2z3a4b5c6d7e8f9g0h1', {
  reason: 'DOCUMENT_FORGERY',
  reportedBy: '[email protected]',
});

await smile.users.clearFraud('user_01h8x9y2z3a4b5c6d7e8f9g0h1', {
  notes: 'Investigated and cleared.',
  reportedBy: '[email protected]',
});

Bank codes

No authentication needed.

const { bankCodes } = await smile.services.bankCodes({ country: 'NG' });

Supported ID types

No authentication needed.

const { idTypes } = await smile.services.supportedIdTypes({ country: 'NG' });

Supported documents

No authentication needed.

const { validDocuments } = await smile.services.supportedDocuments({ countryCode: 'NG' });

ID provider status

const status = await smile.services.idStatus({ country: 'NG', idType: 'NIN' });
status.lastKnownStatus; // "online"

Error handling

Every failure throws a subclass of SmileIDError. Each error carries statusCode, status, message, code (services errors only), requestId, and rawBody.

| Error | When | | ---------------------- | ---------------------------------------------------------- | | InvalidRequestError | 400 or 415 | | ValidationError | Client-side validation, before any request is sent | | AuthenticationError | 401 after one token refresh has already been tried | | PaymentRequiredError | 402, insufficient wallet balance | | PermissionError | 403 | | NotFoundError | 404 (not thrown by verifications.retrieve) | | ConflictError | 409, e.g. replaying a job that is still processing | | PayloadTooLargeError | 413 | | RateLimitError | 429 | | APIError | 5xx | | ConnectionError | Network failure with no HTTP response | | UnexpectedResponseError | A 2xx response whose body is not a JSON object | | TimeoutError | waitUntilComplete deadline passed |

import { PaymentRequiredError, SmileIDError } from '@smileid/usesmileid-nodejs';

try {
  await smile.enhancedKyc.verify({ /* ... */ });
} catch (err) {
  if (err instanceof PaymentRequiredError) {
    // top up the wallet
  } else if (err instanceof SmileIDError) {
    console.error(err.statusCode, err.message);
  }
}

Retries

The SDK retries idempotent operations only (status and services reads, plus the internal token fetch) on connection errors, 408, 429, and 5xx, honouring the Retry-After header. It never retries verification submissions, replay, or fraud reports, and never retries a 409 — those are yours to decide.

Telemetry

Every request carries three headers identifying the SDK: SmileID-Source-SDK: node, SmileID-Source-SDK-Version, and a User-Agent with the runtime version. These are observability metadata only; they are never used for authentication and carry no personal data.

License

MIT — see LICENSE.