> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vouch.expert/llms.txt
> Use this file to discover all available pages before exploring further.

# iOS SDK

> Native Swift SDK for email validation and device fingerprinting on iOS

## Overview

The Vouch iOS SDK provides email validation and device fingerprinting for native iOS applications. Built with Swift, it integrates seamlessly with both SwiftUI and UIKit.

<Note>
  **Package:** `VouchSDK`
  **Platform:** iOS 15.0+
  **Language:** Swift 5.9+
  **Distribution:** Swift Package Manager
</Note>

## Installation

### Swift Package Manager (Recommended)

#### Via Xcode

1. Open your project in Xcode
2. Go to **File → Add Package Dependencies**
3. Enter the repository URL: `https://github.com/Vouch-IN/vouch-sdk-ios`
4. Select version requirements: "Up to Next Major Version" → `0.3.0`
5. Add `VouchSDK` to your target

#### Via Package.swift

Add the package dependency to your `Package.swift`:

```swift Package.swift theme={null}
dependencies: [
    .package(url: "https://github.com/Vouch-IN/vouch-sdk-ios", from: "0.3.0")
]
```

Then add it to your target dependencies:

```swift theme={null}
.target(
    name: "YourTarget",
    dependencies: ["VouchSDK"]
)
```

## Quick Start

```swift theme={null}
import VouchSDK

// Initialize the SDK
let vouch = Vouch(
    projectId: "your-project-id",
    apiKey: "your-client-api-key"
)

// Validate an email
Task {
    let result = await vouch.validate("user@example.com")

    if result.isAllowed {
        print("Valid:", result.email ?? "")
    } else {
        print("Error:", result.errorMessage ?? "Validation failed")
    }
}
```

## SwiftUI Integration

### Basic Form Validation

```swift theme={null}
import SwiftUI
import VouchSDK

struct SignUpView: View {
    @State private var email = ""
    @State private var isValidating = false
    @State private var validationMessage: String?
    @State private var isValid = false

    let vouch = Vouch(
        projectId: "your-project-id",
        apiKey: "your-client-api-key"
    )

    var body: some View {
        VStack(spacing: 20) {
            TextField("Email Address", text: $email)
                .textFieldStyle(.roundedBorder)
                .autocapitalization(.none)
                .keyboardType(.emailAddress)
                .textContentType(.emailAddress)

            Button("Sign Up") {
                validateEmail()
            }
            .disabled(isValidating || email.isEmpty)

            if let message = validationMessage {
                Text(message)
                    .foregroundColor(isValid ? .green : .red)
                    .font(.caption)
            }

            if isValidating {
                ProgressView()
            }
        }
        .padding()
    }

    func validateEmail() {
        isValidating = true
        validationMessage = nil

        Task {
            let result = await vouch.validate(email)

            await MainActor.run {
                isValidating = false

                if result.isAllowed {
                    isValid = true
                    validationMessage = "Email validated"
                } else {
                    isValid = false
                    validationMessage = result.errorMessage ?? "Email validation failed"
                }
            }
        }
    }
}
```

### Real-Time Validation

```swift theme={null}
import SwiftUI
import VouchSDK
import Combine

struct RealtimeEmailField: View {
    @State private var email = ""
    @State private var validationState: ValidationState = .idle
    @State private var cancellable: AnyCancellable?

    let vouch = Vouch(projectId: "your-project-id", apiKey: "your-client-api-key")

    enum ValidationState {
        case idle
        case validating
        case valid
        case invalid(String)
    }

    var body: some View {
        VStack(alignment: .leading) {
            HStack {
                TextField("Email", text: $email)
                    .textFieldStyle(.roundedBorder)
                    .onChange(of: email) { _, newValue in
                        scheduleValidation(email: newValue)
                    }

                switch validationState {
                case .idle:
                    EmptyView()
                case .validating:
                    ProgressView()
                        .scaleEffect(0.7)
                case .valid:
                    Image(systemName: "checkmark.circle.fill")
                        .foregroundColor(.green)
                case .invalid:
                    Image(systemName: "xmark.circle.fill")
                        .foregroundColor(.red)
                }
            }

            if case .invalid(let message) = validationState {
                Text(message)
                    .font(.caption)
                    .foregroundColor(.red)
            }
        }
    }

    func scheduleValidation(email: String) {
        cancellable?.cancel()

        guard !email.isEmpty else {
            validationState = .idle
            return
        }

        validationState = .validating

        cancellable = Just(email)
            .delay(for: .milliseconds(500), scheduler: RunLoop.main)
            .sink { emailToValidate in
                Task {
                    let result = await vouch.validate(emailToValidate)

                    await MainActor.run {
                        if result.isAllowed {
                            validationState = .valid
                        } else {
                            validationState = .invalid(result.errorMessage ?? "Invalid email")
                        }
                    }
                }
            }
    }
}
```

## UIKit Integration

### Basic Usage

```swift theme={null}
import UIKit
import VouchSDK

class SignUpViewController: UIViewController {
    let vouch = Vouch(
        projectId: "your-project-id",
        apiKey: "your-client-api-key"
    )

    @IBOutlet weak var emailTextField: UITextField!
    @IBOutlet weak var signUpButton: UIButton!
    @IBOutlet weak var resultLabel: UILabel!

    @IBAction func signUpButtonTapped(_ sender: UIButton) {
        guard let email = emailTextField.text, !email.isEmpty else {
            resultLabel.text = "Please enter an email address"
            resultLabel.textColor = .systemRed
            return
        }

        signUpButton.isEnabled = false
        resultLabel.text = "Validating..."
        resultLabel.textColor = .systemGray

        Task {
            let result = await vouch.validate(email)

            await MainActor.run {
                signUpButton.isEnabled = true

                if result.isAllowed {
                    resultLabel.text = "Valid email"
                    resultLabel.textColor = .systemGreen
                    // Proceed with sign-up
                } else {
                    resultLabel.text = result.errorMessage ?? "Invalid email"
                    resultLabel.textColor = .systemRed
                }
            }
        }
    }
}
```

## Configuration

### Custom Options

```swift theme={null}
import VouchSDK

let options = VouchOptions(
    endpoint: "https://api.vouch.expert",
    version: .version(1)  // Use /v1/ endpoint
)

let vouch = Vouch(
    projectId: "your-project-id",
    apiKey: "your-client-api-key",
    options: options
)
```

### API Version

Control which API version to use:

```swift theme={null}
// Use latest (unversioned) endpoint
let options = VouchOptions(version: .latest)

// Use specific version
let options = VouchOptions(version: .version(1))  // Uses /v1/
```

## Core Methods

### validate()

Validate an email address with automatic device fingerprinting. Never throws — all errors are returned in the result.

```swift theme={null}
let result = await vouch.validate("user@example.com")

if result.isAllowed {
    print("Valid email:", result.email ?? "")
} else {
    print("Error:", result.errorMessage ?? "Validation failed")
}
```

For more detail, access the full response:

```swift theme={null}
let result = await vouch.validate("user@example.com")

// Access recommendation directly
print("Recommendation:", result.recommendation ?? "none")
print("Signals:", result.signals ?? [])

// Or unwrap the full response data
if let data = result.data {
    switch data {
    case .validation(let response):
        print("Checks:", response.checks)
        print("Metadata:", response.metadata)
    case .error(let errorData):
        print("Error:", errorData.error, "-", errorData.message)
    }
}
```

**Returns:** `ValidationResult`

<ResponseField name="email" type="String?">
  The normalized email address
</ResponseField>

<ResponseField name="error" type="String?">
  Error message if request failed
</ResponseField>

<ResponseField name="data" type="ValidationData?">
  Response data from server (enum with .validation or .error case)
</ResponseField>

<ResponseField name="statusCode" type="Int?">
  HTTP status code
</ResponseField>

**Convenience properties:**

<ResponseField name="isAllowed" type="Bool">
  Whether the validation passed (recommendation is "allow")
</ResponseField>

<ResponseField name="recommendation" type="String?">
  The recommendation string ("allow", "block", or "flag")
</ResponseField>

<ResponseField name="signals" type="[String]?">
  The validation signals array
</ResponseField>

<ResponseField name="errorMessage" type="String?">
  A descriptive error message for any failure scenario, nil if allowed
</ResponseField>

### generateFingerprint()

Get the device fingerprint directly without validating an email:

```swift theme={null}
let fingerprint = try await vouch.generateFingerprint()

print("Device:", fingerprint.hardware.deviceModel)
print("Screen:", "\(fingerprint.hardware.screenWidth)x\(fingerprint.hardware.screenHeight)")
print("OS:", fingerprint.system.osVersion)
print("Fonts:", fingerprint.fonts.fonts.count)
```

**Returns:** `Fingerprint` with the following signals:

<ResponseField name="hardware" type="HardwareSignals">
  Device hardware information (screen, CPU, memory, model)
</ResponseField>

<ResponseField name="fonts" type="FontSignals">
  System fonts and SHA-256 hash
</ResponseField>

<ResponseField name="system" type="SystemSignals">
  iOS version, language, locale, timezone
</ResponseField>

<ResponseField name="storage" type="StorageSignals">
  UserDefaults, Keychain, FileSystem availability
</ResponseField>

<ResponseField name="timestamp" type="Int64">
  Unix timestamp in milliseconds
</ResponseField>

<ResponseField name="version" type="String">
  SDK version (e.g., "2.0.0")
</ResponseField>

## Error Handling

The SDK never throws from `validate()`. All errors (network failures, invalid format, API errors) are captured in the `ValidationResult`:

```swift theme={null}
let result = await vouch.validate(email)

if result.isAllowed {
    // Proceed with sign-up
} else {
    // result.errorMessage contains a descriptive error for any failure:
    // - "Invalid email format" (local validation)
    // - "Network error: ..." (connectivity issues)
    // - "Fingerprint generation failed: ..." (device signal error)
    // - "Email blocked: block" (API rejected the email)
    // - API error messages
    showError(result.errorMessage ?? "Validation failed")
}
```

### Accessing Detailed Error Info

For cases where you need more control:

```swift theme={null}
let result = await vouch.validate(email)

if let data = result.data {
    switch data {
    case .validation(let response):
        if response.recommendation == "allow" {
            // Proceed
        } else if response.recommendation == "flag" {
            // Show warning but allow
            showWarning("Please verify your email")
        } else {
            // Blocked
            showError("Email not accepted")
        }
    case .error(let errorData):
        // API returned a structured error
        print("Error code:", errorData.error)   // e.g., "invalid_email"
        print("Message:", errorData.message)    // e.g., "Email format is invalid"
    }
} else if let error = result.error {
    // Network or fingerprint error (no API response)
    print("Error:", error)
    print("Status code:", result.statusCode ?? 0)
}
```

## Privacy & Permissions

### No Permissions Required

The Vouch iOS SDK **does not require any device permissions**. It only accesses publicly available system APIs.

<Warning>
  **Important:** No Info.plist usage descriptions are required. The SDK works without requesting any permissions from the user.
</Warning>

**No permissions needed for:**

* Location
* Camera/Photos
* Contacts
* Microphone
* Bluetooth

### Data Collection

The SDK collects device fingerprint data for fraud prevention. You must disclose this in:

1. **Your app's privacy policy**
2. **App Store privacy nutrition label**
3. **GDPR/CCPA notices** (if applicable)

**What data is collected:**

* Hardware signals: Screen dimensions, CPU cores, memory, device model
* Font signals: System fonts and SHA-256 hash
* System signals: iOS version, language, locale, timezone
* Storage signals: UserDefaults, Keychain, FileSystem availability

<Info>
  No personally identifiable information (PII) is collected. All data is technical device and system information.
</Info>

## Performance

* **Fingerprint Generation:** \~100-500ms (first time)
* **Email Validation:** Instant local validation + network request time
* **Font Collection:** \~200-1000ms (can be disabled)

<Tip>
  The SDK starts fingerprint generation immediately when initialized, so the first `validate()` call can reuse the cached fingerprint.
</Tip>

## Best Practices

### 1. Initialize Early

Initialize the SDK as early as possible to allow fingerprint generation to complete:

```swift theme={null}
@main
struct YourApp: App {
    let vouch = Vouch(projectId: "...", apiKey: "...")

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(vouch)
        }
    }
}
```

### 2. Handle Errors Gracefully

Always handle validation errors without blocking the user:

```swift theme={null}
let result = await vouch.validate(email)

if result.isAllowed {
    // Proceed with sign-up
} else if result.recommendation == "flag" {
    // Show user-friendly warning but allow
    showWarning("Please verify your email")
} else {
    // Don't block the user entirely, allow retry
    showError(result.errorMessage ?? "Validation unavailable, please try again")
}
```

### 3. Use Environment Objects (SwiftUI)

Share the Vouch instance across your app using environment objects:

```swift theme={null}
ContentView()
    .environmentObject(vouch)

// Then in child views:
@EnvironmentObject var vouch: Vouch
```

## API Reference

### Vouch

Main SDK class for email validation and fingerprinting.

```swift theme={null}
public actor Vouch {
    public init(projectId: String, apiKey: String, options: VouchOptions = VouchOptions())

    public func validate(_ email: String) async -> ValidationResult
    public func generateFingerprint() async throws -> Fingerprint
}
```

### VouchOptions

SDK configuration options.

```swift theme={null}
public struct VouchOptions {
    public let endpoint: String  // Default: "https://api.vouch.expert"
    public let version: APIVersion  // Default: .latest
}
```

### APIVersion

API version specification.

```swift theme={null}
public enum APIVersion {
    case latest  // Unversioned endpoint
    case version(Int)  // Versioned endpoint (e.g., /v1/)
}
```

### ValidationAction

Validation action enum used in toggle configuration.

```swift theme={null}
public enum ValidationAction: String, Codable {
    case allow
    case block
    case flag
}
```

### ValidationResult

Email validation response structure with convenience properties.

```swift theme={null}
public struct ValidationResult {
    public let email: String?
    public let error: String?
    public let data: ValidationData?
    public let statusCode: Int?

    // Convenience properties
    public var isAllowed: Bool
    public var recommendation: String?
    public var signals: [String]?
    public var errorMessage: String?
}
```

### ValidationData

Validation response data — enum with two cases.

```swift theme={null}
public enum ValidationData {
    case validation(ValidationResponse)  // Successful validation
    case error(ErrorResponseData)  // API returned an error
}
```

### ValidationResponse

Successful validation response containing checks and recommendation.

```swift theme={null}
public struct ValidationResponse {
    public let checks: [String: CheckResult]
    public let message: String?
    public let metadata: ValidationMetadata
    public let recommendation: String  // "allow", "block", or "flag"
    public let signals: [String]
}
```

### CheckResult

Individual check result.

```swift theme={null}
public struct CheckResult {
    public let error: String?
    public let latency: Int
    public let metadata: [String: JSONValue]?
    public let pass: Bool
}
```

### ValidationMetadata

Metadata about the validation request.

```swift theme={null}
public struct ValidationMetadata {
    public let fingerprintHash: String?  // Device fingerprint hash
    public let previousSignups: Int  // Previous signups from this device
    public let totalLatency: Int  // Total validation latency in ms
}
```

### DeviceData

Device fingerprint data returned in validation results.

```swift theme={null}
public struct DeviceData {
    public let emailsUsed: Int
    public let firstSeen: Int
    public let isKnownDevice: Bool
    public let isNewEmail: Bool
    public let lastSeen: Int?
    public let previousSignups: Int
}
```

### IPData

IP address analysis data.

```swift theme={null}
public struct IPData {
    public let ip: String
    public let isAnonymous: Bool  // True if VPN, Tor, or datacenter IP detected
    public let isFraud: Bool
}
```

### ValidationToggles

Toggle configuration for which validations to run.

```swift theme={null}
public struct ValidationToggles {
    public let alias: ValidationAction?
    public let catchall: ValidationAction?
    public let device: ValidationAction?
    public let disposable: ValidationAction?
    public let ip: ValidationAction?
    public let mx: ValidationAction?
    public let roleEmail: ValidationAction?
    public let smtp: ValidationAction?
    public let syntax: ValidationAction?
}
```

### ErrorResponseData

Error response from the API.

```swift theme={null}
public struct ErrorResponseData {
    public let error: String  // Error code (e.g., "invalid_email")
    public let message: String  // Human-readable error message
}
```

## Support

For issues and questions:

* **GitHub:** [github.com/Vouch-IN/vouch-sdk-ios](https://github.com/Vouch-IN/vouch-sdk-ios)
* **Documentation:** [docs.vouch.in](https://docs.vouch.in)
