
# Getting started: iOS (Validations SDK)
{{<sdk-guides-aside>}}
{{<tags>}}

## **Introduction**

The Truora Validations SDK allows you to integrate identity verification features directly into your native mobile applications. The SDK handles the complexity of capturing the user's identity documents, facial recognition, and liveness detection to verify their identity against backend records.

# **Requirements**

## **Platform Requirements**

* **iOS:** 13.0 or higher

## **Permissions Required**

* Camera access (for document and face capture)  
* Internet access (for API communication)

# **Prerequisites & Authentication**

## **The Key Provider Interface**

To prevent hardcoding sensitive logic, you must implement the `TruoraAPIKeyGetter` interface in your code. This allows you to retrieve the API key from a secure location (like a compiled secret, a secure storage enclave, or an obfuscated string). This interface is how the `sdk` will get access to the `api key` for the validations, the api key must be of type `sdk` (temporary), any other key type will result in an error.

### Sample Swift Implementation

{{<code lang="swift" lang_title="Swift">}}
import Foundation
import TruoraValidationsSDK

/// A standalone implementation of TruoraAPIKeyGetter in Swift.
/// This handles the asynchronous retrieval of the API key for the Validations SDK.
public class SecureApiKeyProvider: TruoraAPIKeyGetter {
    private let apiKey: String
    
    public init(apiKey: String) {
        self.apiKey = apiKey
    }
    
    /**
     * Retrieves the API key. 
     * Marked as 'async throws' to support secure network-based or 
     * disk-encrypted (Keychain) lookups that might fail or take time.
     */
    public func getApiKeyFromSecureStorage() async throws -> String {
        // In a production app, you might fetch this from the iOS Keychain
        // using a library like SwiftKeychainWrapper or LocalAuthentication.
        return apiKey
    }
}
{{</code>}}

# **Installation**

Add the Truora SDK via Swift Package Manager (SPM) or CocoaPods. In either case you must add the Camera usage description to your `Info.plist` file, or the app will crash upon launch.

{{<code lang="text" lang_title="Text">}}
Key: Privacy - Camera Usage DescriptionValue: We need camera access to verify your identity.
{{</code>}}

## **CocoaPods**

Add the `TruoraValidationsSDK`  pod to you Podfile to your target block: 

{{<code lang="text" lang_title="Text">}}
  pod 'TruoraValidationsSDK', '1.0.0'
{{</code>}}

## **SPM**

Add the repository URL (provided by Truora) in Xcode under `File > Add Packages`.

# 

# **Implementation**

The iOS SDK uses a fluent builder pattern and `async/await` compatible flows, though it also supports completion handlers

{{<code lang="swift" lang_title="Swift">}}
import UIKit
import TruoraValidationsSDK

class ViewController: UIViewController, TruoraAPIKeyGetter {

    // 1. Implement the Key Getter
    func getApiKeyFromSecureStorage() async throws -> String {
        // In a production app, you might fetch this from the iOS Keychain
        // using a library like SwiftKeychainWrapper or LocalAuthentication.
    }

    func startValidation() {
        Task {
            // 2. Build the SDK
            let validation = TruoraValidationsSDK.Builder(
                apiKeyGenerator: self, 
                userId: "user_unique_id"
            )
            .withValidation { (face: Face) in
                // Configure Face specific settings
                face
                    .useAutocapture(true)
                    .setSimilarityThreshold(0.95)
                    .setTimeout(60)
			.waitForResults(false)
            }
            .build() // Returns a TruoraValidation<Face> object

            // 3. Start the flow
            await validation.start(from: self) { [weak self] result in
                self?.handleResult(result)
            }
        }
    }

    func handleResult(_ result: TruoraValidationResult<ValidationResult>) {
        switch result {
        case .completed(let validationResult):
            print("Success! ID: \(validationResult.validationId)")
            print("Status: \(validationResult.status)")
            
        case .error(let error):
            print("Validation failed: \(error.localizedDescription)")
            // You can check for specific errors like user cancellation
        }
    }
}
{{</code>}}

# **Configuration Options**

Before implementing validations, let's understand the available configuration options.

Using custom validation config and UI config is optional when building the validation:

- If no `uiConfig` is provided then the validation view will show the standard Truora branding, logo and colors  
- If no `validationConfig` is provided then default values will be applied like:  
  - No waiting for validation results before finishing the validation process for the user  
  - `autocapture` of face/doc when it’s detected will be `on`

More indepth information on the validation configuration options, such as similarity threshold, can be found in the official validations api documentation

- [Face validation API](https://dev.truora.com/validators/face_validation_api/)  
- [Document validation API](https://dev.truora.com/validators/document_validation_api/)

## **User ID**

The `userId` parameter is crucial for initializing the Truora SDK. It provides a unique identifier for the user undergoing the validation process.

* Purpose: The `userId` allows Truora to associate validation attempts and reference data (like previously captured reference faces) with a specific user in your system. It is mandatory for linking the validation results to your user base.

* Best Practice: This ID should be an immutable, non-sensitive identifier from your database (e.g., a UUID or internal user ID) that uniquely identifies the person. Do not use sensitive PII like an email address or national ID number as the `userId`.

## **Language**

The `language` parameter allows you to define the localization of the text and instructions displayed within the validation view.

* Customization: By setting the language, you ensure a user-friendly experience by presenting instructions and feedback in the user's preferred language.

* Available Options: The SDK supports several languages. You should provide the language code as a two-letter string (e.g., "es" for Spanish, "en" for English, "pt" for Portuguese). If no language is provided, the SDK will typically default to English or attempt to use the device's system language, though providing an explicit language is recommended.

## **Face configuration**

To create a face validation you just need to add it when building the validation object `withValidation`, you can just return the validation config inside the lambda function if you do not want to use any custom configuration

To specify that the validation type you want to use is `Face`, then explicitly type the lambda function as accepting a `Face` config validation type

### Swift Example

{{<code lang="swift" lang_title="Swift">}}
// 1. Build the SDK
let validation = TruoraValidationsSDK.Builder(
    apiKeyGenerator: self,
    userId: "user_unique_id"
)
.withValidation { (face: Face) in
    // Configure Face specific settings
    face
        .useAutocapture(true)
        .setSimilarityThreshold(0.95)
        .setTimeout(60)
        .waitForResults(false)
}
.build() // Returns a TruoraValidation<Face> object
{{</code>}}

### Configuration Parameters:

{{<table "table w-auto small m-auto text-center table-striped table-bordered">}}
| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| **useAutocapture** | bool | true | Automatically capture when face is properly detected |
| **similarityThreshold** | double | 0.8 | Minimum similarity score required (0.0 - 1.0) |
| **timeout** | int | 60 | Maximum time in seconds for the validation |
| **waitForResults** | bool | false | Wait for API response before closing the view |
| **referenceFace** | ReferenceFace | null | Optional reference image for comparison |
{{</table>}}

## 

## **Document configuration**

To create a face validation you just need to added it when building the validation object `withValidation`, you can just return the validation config inside the lambda function if you do not want to use any custom configuration

To specify that the validation type you want to use is `Document`, then explicitly type the lambda function as accepting a `Document` config validation type

### Swift Example

{{<code lang="swift" lang_title="Swift">}}
// 1. Build the SDK
let validation = TruoraValidationsSDK.Builder(
    apiKeyGenerator: self,
    userId: "user_unique_id"
)
.withValidation { (doc: Document) in
    doc.useAutocapture(true)
        .setSimilarityThreshold(0.95)
        .setTimeout(60)
        .waitForResults(false)
        .setCountry(.co) // Country code (e.g., co, mx, pe)
        .setDocumentType(.national_id) // Type of document
}
.build() // Returns a TruoraValidation<Document> object
{{</code>}}

### Configuration Parameters:

{{<table "table w-auto small m-auto text-center table-striped table-bordered">}}
| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| **country** | Country | null | Country code for document validation (if not set, user selects) |
| **documentType** | DocumentType | null | Type of document (if not set, user selects) |
| **useAutocapture** | bool | true | Automatically capture when document is detected. **Not supported for passport** — the SDK will raise an error if autocapture is enabled with `.passport` document type |
| **timeout** | int | 90 | Maximum time in seconds for the validation |
| **waitForResults** | bool | false | Wait for API response before closing the view |
{{</table>}}

### Available Countries and Document Types:

{{<table "table w-auto small m-auto text-center table-striped table-bordered">}}
| Country | Code | Supported Document Types |
| --- | --- | --- |
| Argentina | Country.ar | nationalId |
| Brazil | Country.br | cnh, generalRegistration |
| Chile | Country.cl | nationalId, foreignId, driverLicense, passport |
| Colombia | Country.co | nationalId, foreignId, rut, ppt, passport, identityCard, temporaryNationalId |
| Costa Rica | Country.cr | nationalId, foreignId |
| El Salvador | Country.sv | nationalId, foreignId, passport |
| Mexico | Country.mx | nationalId, foreignId, passport |
| Peru | Country.pe | nationalId, foreignId |
| Venezuela | Country.ve | nationalId |
| All | Country.all | passport |
{{</table>}}

# **Understanding SDK Results**

Before implementing validations, it's important to understand what results you'll receive and how to handle them.

The SDK returns a TruoraValidationResult which can be:

* **completed**: Validation process finished (contains ValidationResult)  
* **canceled:** Validation process canceled by the user (could contain ValidationResult)  
* **error**: SDK error occurred (contains TruoraError)

## **Validation Result Object**

The plugin returns ValidationResult objects with the following structure:

{{<code lang="swift" lang_title="Swift">}}
public struct ValidationResult: Codable, Equatable {
    public let validationId: String // Unique ID for this validation
    public let status: ValidationStatus // Status of the validation
    public let confidence: Double?

    /// Full validation detail from the API response.
    /// Available when the result comes from polling (not for canceled/pending results).
    public let detail: ValidationDetail?
}
{{</code>}}

## **Validation Statuses**

{{<table "table w-auto small m-auto text-center table-striped table-bordered">}}
| Status | Description | When it occurs |
| --- | --- | --- |
| **ValidationStatus.success** | Validation passed | User successfully passed the validation |
| **ValidationStatus.failure** | Validation failed | User did not pass the validation criteria |
| **ValidationStatus.pending** | Awaiting processing | Validation is being process |
{{</table>}}

## **TruoraException Types**

When the SDK returns **failed**, the error can be one of three types:

{{<table "table w-auto small m-auto text-center table-striped table-bordered">}}
| Exception Type | Description |
| --- | --- |
| **TruoraException.sdk** | Internal SDK error (configuration, permissions, user actions) |
| **TruoraException.validationApi** | Error from the Truora Validation API |
| **TruoraException.network** | Network connectivity error |
{{</table>}}

Common SDK Error Types (SDKErrorType):

{{<table "table w-auto small m-auto text-center table-striped table-bordered">}}
| Error Type | Code | Description |
| --- | --- | --- |
| **cameraPermissionError** | 20011 | Camera permission was denied |
| **invalidApiKey** | 20017 | API key is invalid or expired |
| **invalidConfiguration** | 20024 | SDK configuration is invalid |
| **networkError** | 20025 | Network connection failed |
| **uploadFailed** | 20026 | Failed to upload captured media |
{{</table>}}

## **Handling Example**

{{<code lang="swift" lang_title="Swift">}}
func handleResult(result: ValidationResult) {
switch result {
  case .completed(let validation):
      print('Validation ID: \(validation.validationId)');
      print('Status: \(validation.status.rawValue)');
      if (validation.status ==.success) {
        // User passed the validation
        navigateToSuccessScreen();
      } else if (validation.status == ValidationStatus.failure) {
        // User did not pass the validation criteria
        showRetryDialog();
      }
      break;

  case .error(let err):
      // SDK error occurred - handle based on error type 
      handleSdkError(err)
	break
  case .canceled(let partialResult):
      // User cancelled - just go back 
      goToPrevScreen()
	break
  }
}


private func handleSDKError(_ error: TruoraException) {
    switch error {
    case .sdk(let sdkError):
        switch sdkError.type {
        case .cameraPermissionError:
            // Camera permission denied — prompt user to grant it
            showAlert(
                title: "Camera Permission Required",
                message: "Please enable camera access in Settings to continue."
            )
        default:
            showAlert(
                title: "SDK Error (\(sdkError.code))",
                message: sdkError.errorDescription ?? "Unknown SDK error"
            )
        }

    case .network(let message, _):
        showAlert(
            title: "Connection Error",
            message: "Connection error: \(message)"
        )

    case .validationApi(let apiError):
        showAlert(
            title: "API Error",
            message: "API error: \(apiError.errorDescription ?? "Unknown API error")"
        )
    }
}
{{</code>}}

# **Implementation**

Now that you understand configurations and result handling, let's implement validations.

## **Basic Face Validation**

Here's a complete example of implementing face validation in SwiftUI:

{{<code lang="swift" lang_title="Swift">}}
import SwiftUI
import TruoraValidationsSDK

struct ValidationScreen: View {
    @StateObject private var viewModel = ValidationViewModel()

    var body: some View {
        VStack(spacing: 40) {
            Text(viewModel.validationStatus)
                .font(.title3)
                .multilineTextAlignment(.center)
                .padding(.horizontal)

            Button {
                viewModel.startFaceValidation()
            } label: {
                if viewModel.isValidating {
                    ProgressView()
                        .tint(.white)
                } else {
                    Text("Start Face Validation")
                }
            }
            .disabled(viewModel.isValidating)
            .padding(.horizontal, 32)
            .padding(.vertical, 16)
            .background(viewModel.isValidating ? Color.gray : Color.blue)
            .foregroundColor(.white)
            .cornerRadius(8)
        }
        .alert(isPresented: $viewModel.showAlert) {
            Alert(
                title: Text(viewModel.alertTitle),
                message: Text(viewModel.alertMessage),
                dismissButton: .default(Text("OK"))
            )
        }
    }
}

// MARK: - ViewModel

@MainActor
class ValidationViewModel: ObservableObject {
    @Published var validationStatus = "Ready to start validation"
    @Published var isValidating = false
    @Published var showAlert = false
    @Published var alertTitle = ""
    @Published var alertMessage = ""

    private let apiKey = "YOUR_API_KEY"

    func startFaceValidation() {
        isValidating = true
        validationStatus = "Starting face validation..."

        let validation = TruoraValidationsSDK.Builder(
            apiKeyGenerator: self,
            userId: "ios-test-user"
        )
        .withConfig { config in
            config
                .setPrimaryColor("#435AE0")
                .setSurfaceColor("#FFFFFF")
                .setErrorColor("#FF5454")
                .setLogo("https://your-cdn.com/logo.png", width: 120, height: 40)
        }
        .withValidation { (face: Face) in
            face
                .useAutocapture(true)
                .setSimilarityThreshold(0.8)
                .waitForResults(true)
            return face
        }
        .build()

        Task { @MainActor in
            defer { isValidating = false }

            guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
                  let rootViewController = windowScene.windows.first?.rootViewController else {
                validationStatus = "Could not find root view controller"
                return
            }

            await validation.start(from: rootViewController) { [weak self] result in
                self?.handleValidationResult(result)
            }
        }
    }

    private func handleValidationResult(_ result: TruoraValidationResult<ValidationResult>) {
        switch result {
        case .completed(let validation):
            switch validation.status {
            case .success:
                validationStatus = "Success! ID: \(validation.validationId)"
            case .failure:
                validationStatus = "Validation failed. Please try again."
            case .pending:
                validationStatus = "Processing..."
            default:
                validationStatus = "Status: \(validation.status.rawValue)"
            }

        case .error(let error):
            // Detailed error handling using pattern matching
            switch error {
            case .sdk(let sdkError):
                switch sdkError.type {
                case .cameraPermissionError:
                    validationStatus = "Camera permission required"
                case .networkError:
                    validationStatus = "Connection error: \(sdkError.errorDescription ?? "")"
                default:
                    validationStatus = "Error: \(sdkError.errorDescription ?? "Unknown")"
                }

            case .network(let message, _):
                validationStatus = "Connection error: \(message)"

            case .validationApi(let apiError):
                validationStatus = "API error: \(apiError.errorDescription ?? "Unknown")"
            }

        case .canceled(let partialResult):
            if let partialResult {
                validationStatus = "Canceled. Partial ID: \(partialResult.validationId)"
            } else {
                validationStatus = "Validation canceled by user"
            }
        }
    }

    private func showAlert(title: String, message: String) {
        alertTitle = title
        alertMessage = message
        showAlert = true
    }
}

// MARK: - API Key Provider

extension ValidationViewModel: TruoraAPIKeyGetter {
    func getApiKeyFromSecureLocation() async throws -> String {
        apiKey
    }
}
{{</code>}}

## **Document Validation**

For document validation, use the DocumentValidationConfig:

{{<code lang="swift" lang_title="Swift">}}
    func startDocumentValidation() {
        isValidating = true
        validationStatus = "Starting document validation..."

        let validation = TruoraValidationsSDK.Builder(
            apiKeyGenerator: self,
            userId: "ios-test-user"
        )
        .withConfig { config in
            config
                .setPrimaryColor("#435AE0")
                .setSurfaceColor("#FFFFFF")
                .setErrorColor("#FF5454")
                .setLogo("https://your-cdn.com/logo.png", width: 120, height: 40)
        }
        .withValidation { (face: Face) in
            face
                .useAutocapture(true)
                .waitForResults(true)
            return face
        }
        .build()

        Task { @MainActor in
            defer { isValidating = false }

            guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
                  let rootViewController = windowScene.windows.first?.rootViewController else {
                validationStatus = "Could not find root view controller"
                return
            }

            await validation.start(from: rootViewController) { [weak self] result in
                self?.handleValidationResult(result)
            }
        }
    }
{{</code>}}

# **Troubleshooting**

Common Issues

1. **Build fails on iOS**  
   Solution: Run cd ios && pod install and ensure your iOS deployment target is set to 13.0 or higher in both Podfile and Xcode project settings.

2. **"API Key Invalid" error**  
   Solution: Verify your API key is correct and has the proper grants (generator or sdk type). Check that it hasn't expired.

3. **Validation timeout**  
   Solution: Increase the timeout value in the config, or ensure the device has a stable internet connection.