Skip to content

Repository files navigation

Smile ID Java SDK

Maven Central CI License

Official Smile ID server-side SDK for Java — V3 APIs.

This repository is under active development. The SDK is not yet published, and the API surface may change without notice.

  • Group: com.smileidentity
  • Artifact: usesmileid-java
  • Requires Java 11 or later

Install

Not yet published to Maven Central. Once published:

Gradle:

implementation("com.smileidentity:usesmileid-java:12.0.0")

Maven:

<dependency>
  <groupId>com.smileidentity</groupId>
  <artifactId>usesmileid-java</artifactId>
  <version>12.0.0</version>
</dependency>

Until then, build from source with ./gradlew build and use the jar from build/libs/.

Create a client

Construct one SmileID client with your partner id and API key. The SDK manages authentication for you: it fetches an internal token, caches it and refreshes it when it expires. You never handle tokens yourself.

import com.smileidentity.client.Environment;
import com.smileidentity.client.SmileID;

SmileID smile = SmileID.builder()
    .partnerId("1234")
    .apiKey(System.getenv("SMILE_API_KEY"))
    .environment(Environment.SANDBOX)
    .defaultCallbackUrl("https://app.example.com/callback")
    .build();

Configuration options:

Option Default Notes
partnerId required Numeric string, no leading zeros
apiKey required Partner API key
environment SANDBOX SANDBOX or PRODUCTION
defaultCallbackUrl unset Used when a call omits callbackUrl; must be https
baseUrl derived Explicit override; wins over environment; must be https
timeout 30 seconds Per-request total timeout
maxRetries 2 Idempotent operations only
httpClient SDK default Inject your own OkHttpClient

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

environment only names the sandbox and production. To reach any other Smile ID host — a development API, for instance — set baseUrl, which wins over environment:

SmileID smile = SmileID.builder()
    .partnerId("2")
    .apiKey(System.getenv("SMILE_API_KEY"))
    .baseUrl("https://your-environment.example.com")
    .build();

The SDK only talks https. baseUrl must be an absolute https URL with no query or fragment — the builder rejects anything else and there is no insecure override. Callback URLs (defaultCallbackUrl and per-request callbackUrl values) must be https too: the default is checked when you build the client, per-request values are checked before any request is sent.

Environments

The client targets the sandbox by default. Select production explicitly:

  • Environment.SANDBOXhttps://testapi.smileidentity.com
  • Environment.PRODUCTIONhttps://api.smileidentity.com

Shared models

Most verification calls need end-user details and consent:

import com.smileidentity.generated.models.Consent;
import com.smileidentity.generated.models.UserDetails;
import java.time.Instant;

UserDetails user = UserDetails.builder()
    .givenNames("Amina Fatou")
    .lastName("Clearwater")
    .email("amina.clearwater@example.com")   // at least one of email or phoneNumber is required
    .build();

Consent consent = Consent.granted(Instant.now(), "EN", "https://example.com/privacy");

Non-production environments match test identities on given names, last name and email. The identity above is a recognised test identity and resolves to clear; an unrecognised one resolves to block.

Binary inputs (selfies, liveness frames, documents) accept a File, a byte[] or an InputStream via BinaryInput.of(...), with optional .withFilename(...) and .withContentType(...).

Operations

Enhanced KYC

import com.smileidentity.generated.models.AcceptedResponse;
import com.smileidentity.generated.models.EnhancedKycParams;

AcceptedResponse accepted = smile.enhancedKyc().verify(EnhancedKycParams.builder()
    .country("NG").idType("NIN").idNumber("12345678901")
    .userDetails(user)
    .consent(consent)
    .userId("user_01h8x9y2z3a4b5c6d7e8f9g0h1")
    .build());
accepted.getJobId();     // "job_..."
accepted.isAccepted();   // true — normalizes "Accepted" and "accepted"

Document verification

import com.smileidentity.generated.models.DocumentVerificationParams;
import com.smileidentity.helpers.BinaryInput;
import java.io.File;
import java.util.Arrays;
import java.util.List;

List<BinaryInput> livenessFrames = Arrays.asList(
    BinaryInput.of(new File("live1.jpg")),
    BinaryInput.of(new File("live2.jpg")),
    BinaryInput.of(new File("live3.jpg")),
    BinaryInput.of(new File("live4.jpg")),
    BinaryInput.of(new File("live5.jpg")),
    BinaryInput.of(new File("live6.jpg")));   // 6 to 8 frames

AcceptedResponse accepted = smile.documents().verify(DocumentVerificationParams.builder()
    .selfieImage(BinaryInput.of(new File("selfie.jpg")))
    .livenessImages(livenessFrames)
    .document(BinaryInput.of(new File("doc.jpg")))
    .country("NG")                             // idType optional: auto-classified
    .userDetails(user)
    .consent(consent)
    .build());

Enhanced document verification

Same shape as document verification, but idType is required:

import com.smileidentity.generated.models.EnhancedDocumentVerificationParams;

AcceptedResponse accepted = smile.documents().verifyEnhanced(
    EnhancedDocumentVerificationParams.builder()
        .selfieImage(BinaryInput.of(new File("selfie.jpg")))
        .livenessImages(livenessFrames)
        .document(BinaryInput.of(new File("doc.jpg")))
        .country("NG").idType("PASSPORT")
        .userDetails(user)
        .consent(consent)
        .build());

Biometric KYC

import com.smileidentity.generated.models.BiometricKycParams;

AcceptedResponse accepted = smile.biometricKyc().verify(BiometricKycParams.builder()
    .selfieImage(BinaryInput.of(new File("selfie.jpg")))
    .livenessImages(livenessFrames)
    .country("NG").idType("NIN").idNumber("12345678901")
    .userDetails(user)
    .consent(consent)
    .build());

Biometric enrollment

import com.smileidentity.generated.models.EnrollParams;

AcceptedResponse accepted = smile.biometric().enroll(EnrollParams.builder()
    .selfieImage(BinaryInput.of(new File("selfie.jpg")))
    .livenessImages(livenessFrames)
    .userDetails(user)
    .consent(consent)
    .userId("user-42")
    .build());

Biometric authentication

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

import com.smileidentity.generated.models.AuthenticationParams;

AcceptedResponse accepted = smile.biometric().authenticate(AuthenticationParams.builder()
    .userId("user-42")
    .selfieImage(BinaryInput.of(new File("selfie.jpg")))
    .livenessImages(livenessFrames)
    .userDetails(user)
    .consent(consent)
    .build());

Selfie compare

import com.smileidentity.generated.models.CompareParams;
import com.smileidentity.generated.models.ComparisonImageType;

AcceptedResponse accepted = smile.biometric().compare(CompareParams.builder()
    .selfieImage(BinaryInput.of(new File("selfie.jpg")))
    .comparisonImage(BinaryInput.of(new File("id_photo.jpg")))
    .comparisonImageType(ComparisonImageType.ID_PHOTO)
    .userDetails(user)
    .consent(consent)
    .build());

Job status

import com.smileidentity.generated.models.JobStatus;

JobStatus status = smile.verifications().retrieve("job_01h2xcejqtf2nbrexx3vqjhp41");
status.isComplete();    // reached a decision
status.isProcessing();  // still running
status.isNotFound();    // a 404 returns this status instead of raising
status.getStatus();     // "processing", or the decision: "clear", "block", "attention", "error"

While a job runs, status is processing. When it finishes, status carries the decision itself and message is a plain sentence such as Job completed. There is no literal complete status — read the decision from status, not from message. isComplete() is true for any status other than processing and not_found.

Wait for completion

import com.smileidentity.helpers.WaitOptions;
import java.time.Duration;

JobStatus done = smile.verifications().waitUntilComplete(
    accepted.getJobId(),
    WaitOptions.builder()
        .interval(Duration.ofSeconds(2))
        .timeout(Duration.ofSeconds(120))
        .treatNotFoundAsPending(true)
        .build());

done.getStatus();  // "clear", "block", "attention" or "error"

Polls while the job is processing and returns as soon as it reaches a decision. Raises com.smileidentity.errors.TimeoutException if the deadline passes.

Replay a callback

import com.smileidentity.generated.models.ReplayCallbackResponse;
import com.smileidentity.generated.models.ReplayParams;

ReplayCallbackResponse replayed = smile.verifications().replay(
    "job_01h2xcejqtf2nbrexx3vqjhp41",
    ReplayParams.builder().callbackUrl("https://app.example.com/callback").build());

A 409 means the verification is still processing; it raises ConflictException and is never retried automatically.

Report user fraud

import com.smileidentity.generated.models.FraudReason;
import com.smileidentity.generated.models.ReportFraudParams;

smile.users().reportFraud("user-42", ReportFraudParams.builder()
    .isFraud(true)
    .reason(FraudReason.ACCOUNT_TAKEOVER)
    .notes("Suspicious takeover pattern")
    .reportedBy("fraud-team@example.com")
    .build());

// Convenience wrappers:
smile.users().flagFraud("user-42", FraudReason.DOCUMENT_FORGERY, null, "fraud-team@example.com");
smile.users().clearFraud("user-42", "False positive after review", "fraud-team@example.com");

Services

import com.smileidentity.generated.models.SupportedDocumentsParams;

// No authentication needed for these three:
smile.services().bankCodes("NG");
smile.services().supportedIdTypes("NG");
smile.services().supportedDocuments(SupportedDocumentsParams.builder()
    .continent("AFRICA").countryCode("NG").locale("en-GB").build());

// Token required:
smile.services().idStatus("NG", "NIN");

Error handling

All errors extend com.smileidentity.errors.SmileIDException and expose getStatusCode(), getStatus(), getMessage(), getCode(), getRequestId() and getRawBody().

Class When
InvalidRequestException HTTP 400, 415
ValidationException Client-side validation, raised before sending
AuthenticationException HTTP 401 (after one automatic token refresh)
PaymentRequiredException HTTP 402: insufficient wallet balance
PermissionException HTTP 403
NotFoundException HTTP 404 (never from verifications().retrieve)
ConflictException HTTP 409: never auto-retried
PayloadTooLargeException HTTP 413
RateLimitException HTTP 429
ApiException HTTP 5xx
UnexpectedResponseException A 2xx response body that is not a JSON object
ConnectionException Network failure or timeout, no HTTP response
TimeoutException waitUntilComplete deadline passed
import com.smileidentity.errors.PaymentRequiredException;
import com.smileidentity.errors.SmileIDException;

try {
  smile.enhancedKyc().verify(params);
} catch (PaymentRequiredException e) {
  // top up the wallet
} catch (SmileIDException e) {
  System.err.println(e.getStatusCode() + ": " + e.getMessage());
}

Retries: idempotent operations (status and services reads, plus the internal token fetch) are retried automatically on connection errors and HTTP 408, 429 and 5xx, with exponential backoff and support for Retry-After. Job-creating POSTs are never retried automatically — a connection failure surfaces as ConnectionException and the caller decides.

Telemetry

Every request carries SmileID-Source-SDK: java, SmileID-Source-SDK-Version and a User-Agent string identifying the SDK and Java runtime versions. These headers are observability metadata only; they are never used for authentication.

Contributing

See AGENTS.md for how the codebase is laid out and how to run the test suite.

Security

See SECURITY.md for how to report a vulnerability.

License

Licensed under the MIT License.

About

SmileID SDK for Java

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages