> ## 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.

# Android SDK

> Native Kotlin SDK for email validation and device fingerprinting on Android

## Overview

The Vouch Android SDK provides email validation and device fingerprinting for native Android applications. Built with Kotlin, it integrates seamlessly with both Jetpack Compose and traditional Views.

<Note>
  **Package:** `com.github.Vouch-IN:vouch-sdk-android`
  **Platform:** Android 8.0+ (API 26+)
  **Language:** Kotlin 1.9+
  **Distribution:** JitPack (Gradle/Maven)
</Note>

## Installation

### JitPack (Recommended)

Add JitPack repository to your project's `settings.gradle.kts`:

```kotlin settings.gradle.kts theme={null}
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        maven { url = uri("https://jitpack.io") }
    }
}
```

Add the dependency to your app's `build.gradle.kts`:

```kotlin build.gradle.kts theme={null}
dependencies {
    implementation("com.github.Vouch-IN:vouch-sdk-android:vouch-sdk-android-v0.1.12")
}
```

### Permissions

Add internet permission to your `AndroidManifest.xml`:

```xml AndroidManifest.xml theme={null}
<uses-permission android:name="android.permission.INTERNET" />
```

<Info>
  **No other permissions required.** The SDK only needs internet access to communicate with the Vouch API.
</Info>

## Quick Start

```kotlin theme={null}
import expert.vouch.sdk.Vouch
import kotlinx.coroutines.launch

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

// Validate an email
lifecycleScope.launch {
    val result = vouch.validate("user@example.com")

    if (result.isAllowed) {
        println("Valid: ${result.email}")
    } else {
        println("Error: ${result.errorMessage}")
    }
}
```

## Jetpack Compose Integration

### Basic Form Validation

```kotlin theme={null}
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import expert.vouch.sdk.Vouch
import kotlinx.coroutines.launch

@Composable
fun SignUpScreen() {
    val context = LocalContext.current
    val scope = rememberCoroutineScope()

    val vouch = remember {
        Vouch(
            context = context.applicationContext,
            projectId = "your-project-id",
            apiKey = "your-client-api-key"
        )
    }

    var email by remember { mutableStateOf("") }
    var isValidating by remember { mutableStateOf(false) }
    var validationMessage by remember { mutableStateOf<String?>(null) }
    var isValid by remember { mutableStateOf(false) }

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        verticalArrangement = Arrangement.spacedBy(16.dp)
    ) {
        OutlinedTextField(
            value = email,
            onValueChange = {
                email = it
                validationMessage = null
            },
            label = { Text("Email Address") },
            modifier = Modifier.fillMaxWidth(),
            enabled = !isValidating,
            singleLine = true
        )

        Button(
            onClick = {
                scope.launch {
                    isValidating = true
                    validationMessage = null

                    val result = vouch.validate(email)

                    isValidating = false

                    if (result.isAllowed) {
                        isValid = true
                        validationMessage = "Email validated"
                    } else {
                        isValid = false
                        validationMessage = result.errorMessage ?: "Email validation failed"
                    }
                }
            },
            enabled = !isValidating && email.isNotEmpty(),
            modifier = Modifier.fillMaxWidth()
        ) {
            Text(if (isValidating) "Validating..." else "Sign Up")
        }

        validationMessage?.let { message ->
            Text(
                text = message,
                color = if (isValid) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.error,
                style = MaterialTheme.typography.bodySmall
            )
        }

        if (isValidating) {
            LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
        }
    }
}
```

### Real-Time Validation

```kotlin theme={null}
import androidx.compose.runtime.*
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

@Composable
fun RealtimeEmailField() {
    val context = LocalContext.current
    val scope = rememberCoroutineScope()

    val vouch = remember {
        Vouch(
            context = context.applicationContext,
            projectId = "your-project-id",
            apiKey = "your-client-api-key"
        )
    }

    var email by remember { mutableStateOf("") }
    var validationState by remember { mutableStateOf<ValidationState>(ValidationState.Idle) }
    var validationJob by remember { mutableStateOf<Job?>(null) }

    LaunchedEffect(email) {
        validationJob?.cancel()

        if (email.isEmpty()) {
            validationState = ValidationState.Idle
            return@LaunchedEffect
        }

        validationState = ValidationState.Validating

        validationJob = scope.launch {
            delay(500) // Debounce

            val result = vouch.validate(email)

            if (result.isAllowed) {
                validationState = ValidationState.Valid
            } else {
                validationState = ValidationState.Invalid(result.errorMessage ?: "Invalid email")
            }
        }
    }

    Row(
        modifier = Modifier.fillMaxWidth(),
        horizontalArrangement = Arrangement.spacedBy(8.dp),
        verticalAlignment = Alignment.CenterVertically
    ) {
        OutlinedTextField(
            value = email,
            onValueChange = { email = it },
            label = { Text("Email") },
            modifier = Modifier.weight(1f),
            singleLine = true,
            isError = validationState is ValidationState.Invalid
        )

        when (validationState) {
            ValidationState.Idle -> {}
            ValidationState.Validating -> CircularProgressIndicator(modifier = Modifier.size(24.dp))
            ValidationState.Valid -> Icon(
                imageVector = Icons.Default.CheckCircle,
                contentDescription = "Valid",
                tint = MaterialTheme.colorScheme.primary
            )
            is ValidationState.Invalid -> Icon(
                imageVector = Icons.Default.Error,
                contentDescription = "Invalid",
                tint = MaterialTheme.colorScheme.error
            )
        }
    }

    if (validationState is ValidationState.Invalid) {
        Text(
            text = (validationState as ValidationState.Invalid).message,
            color = MaterialTheme.colorScheme.error,
            style = MaterialTheme.typography.bodySmall
        )
    }
}

sealed class ValidationState {
    object Idle : ValidationState()
    object Validating : ValidationState()
    object Valid : ValidationState()
    data class Invalid(val message: String) : ValidationState()
}
```

## Traditional View/Activity Integration

### Basic Usage

```kotlin theme={null}
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import expert.vouch.sdk.Vouch
import kotlinx.coroutines.launch

class SignUpActivity : AppCompatActivity() {
    private val vouch by lazy {
        Vouch(
            context = applicationContext,
            projectId = "your-project-id",
            apiKey = "your-client-api-key"
        )
    }

    private lateinit var emailEditText: EditText
    private lateinit var signUpButton: Button
    private lateinit var resultTextView: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_signup)

        emailEditText = findViewById(R.id.emailEditText)
        signUpButton = findViewById(R.id.signUpButton)
        resultTextView = findViewById(R.id.resultTextView)

        signUpButton.setOnClickListener {
            val email = emailEditText.text.toString()

            if (email.isEmpty()) {
                resultTextView.text = "Please enter an email address"
                resultTextView.setTextColor(ContextCompat.getColor(this, android.R.color.holo_red_dark))
                return@setOnClickListener
            }

            signUpButton.isEnabled = false
            resultTextView.text = "Validating..."
            resultTextView.setTextColor(ContextCompat.getColor(this, android.R.color.darker_gray))

            lifecycleScope.launch {
                val result = vouch.validate(email)

                signUpButton.isEnabled = true

                if (result.isAllowed) {
                    resultTextView.text = "Valid email"
                    resultTextView.setTextColor(ContextCompat.getColor(this@SignUpActivity, android.R.color.holo_green_dark))
                    // Proceed with sign-up
                } else {
                    resultTextView.text = result.errorMessage ?: "Invalid email"
                    resultTextView.setTextColor(ContextCompat.getColor(this@SignUpActivity, android.R.color.holo_red_dark))
                }
            }
        }
    }
}
```

### With ViewModel (MVVM Pattern)

```kotlin theme={null}
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import expert.vouch.sdk.Vouch
import expert.vouch.sdk.models.ValidationResult
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch

class SignUpViewModel(private val vouch: Vouch) : ViewModel() {
    private val _validationState = MutableStateFlow<ValidationResult?>(null)
    val validationState: StateFlow<ValidationResult?> = _validationState

    private val _isValidating = MutableStateFlow(false)
    val isValidating: StateFlow<Boolean> = _isValidating

    fun validateEmail(email: String) {
        viewModelScope.launch {
            _isValidating.value = true
            _validationState.value = null

            val result = vouch.validate(email)

            _validationState.value = result
            _isValidating.value = false
        }
    }
}
```

## Configuration

### Custom Options

```kotlin theme={null}
import expert.vouch.sdk.Vouch
import expert.vouch.sdk.models.VouchOptions
import expert.vouch.sdk.models.ApiVersion

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

val vouch = Vouch(
    context = applicationContext,
    projectId = "your-project-id",
    apiKey = "your-client-api-key",
    options = options
)
```

### API Version

Control which API version to use:

```kotlin theme={null}
// Use latest (unversioned) endpoint
val options = VouchOptions(version = ApiVersion.Latest)

// Use specific version
val options = VouchOptions(version = ApiVersion.Version(1))  // Uses /v1/
```

## Core Methods

### validate()

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

```kotlin theme={null}
val result = vouch.validate("user@example.com")

if (result.isAllowed) {
    println("Valid email: ${result.email}")
} else {
    println("Error: ${result.errorMessage}")
}
```

For more detail, access the full response:

```kotlin theme={null}
val result = vouch.validate("user@example.com")

// Access recommendation directly
println("Recommendation: ${result.recommendation}")
println("Signals: ${result.signals}")

// Or unwrap the full response data
if (result.data != null) {
    when (val data = result.data) {
        is ValidationData.Validation -> {
            println("Checks: ${data.checks}")
            println("Metadata: ${data.metadata}")
        }
        is ValidationData.Error -> {
            println("Error: ${data.response.error} - ${data.response.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 (sealed class with Validation or Error)
</ResponseField>

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

**Convenience properties:**

<ResponseField name="isAllowed" type="Boolean">
  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="List<String>?">
  The validation signals list
</ResponseField>

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

### generateFingerprint()

Get the device fingerprint directly without validating an email:

```kotlin theme={null}
val fingerprint = vouch.generateFingerprint()

println("Device: ${fingerprint.hardware.deviceModel}")
println("Manufacturer: ${fingerprint.hardware.manufacturer}")
println("Screen: ${fingerprint.hardware.screenWidth}x${fingerprint.hardware.screenHeight}")
println("OS: Android ${fingerprint.system.osVersion}")
```

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

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

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

<ResponseField name="system" type="SystemSignals">
  Android version, SDK level, language, locale, timezone
</ResponseField>

<ResponseField name="storage" type="StorageSignals">
  SharedPreferences, KeyStore, FileSystem availability
</ResponseField>

<ResponseField name="timestamp" type="Long">
  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`:

```kotlin theme={null}
val result = 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:

```kotlin theme={null}
val result = vouch.validate(email)

if (result.data != null) {
    when (val data = result.data) {
        is ValidationData.Validation -> {
            when (data.recommendation) {
                "allow" -> {
                    // Proceed
                }
                "flag" -> {
                    // Show warning but allow
                    showWarning("Please verify your email")
                }
                else -> {
                    // Blocked
                    showError("Email not accepted")
                }
            }
        }
        is ValidationData.Error -> {
            // API returned a structured error
            println("Error code: ${data.response.error}")   // e.g., "invalid_email"
            println("Message: ${data.response.message}")    // e.g., "Email format is invalid"
        }
    }
} else if (result.error != null) {
    // Network or fingerprint error (no API response)
    println("Error: ${result.error}")
    println("Status code: ${result.statusCode ?: 0}")
}
```

## Privacy & Permissions

### Minimal Permissions

The Vouch Android SDK only requires the **INTERNET** permission:

```xml theme={null}
<uses-permission android:name="android.permission.INTERNET" />
```

<Warning>
  **No dangerous permissions required.** The SDK does not access location, camera, contacts, or any other sensitive data.
</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. **Google Play Data Safety form**
3. **GDPR/CCPA notices** (if applicable)

**What data is collected:**

* Hardware signals: Screen dimensions, CPU cores, memory, device model, manufacturer
* Font signals: System fonts and SHA-256 hash
* System signals: Android version, SDK level, language, locale, timezone
* Storage signals: SharedPreferences, KeyStore, FileSystem availability

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

## ProGuard/R8

The SDK is fully compatible with ProGuard and R8. Obfuscation rules are included automatically in the library.

No additional configuration is required.

## 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 with Application Context

Always use `applicationContext` to avoid memory leaks:

```kotlin theme={null}
val vouch = Vouch(
    context = applicationContext,  // Good
    // context = this,  // Bad (Activity context)
    projectId = "...",
    apiKey = "..."
)
```

### 2. Use Dependency Injection

Inject the Vouch instance using Hilt or Koin:

```kotlin theme={null}
// Hilt example
@Module
@InstallIn(SingletonComponent::class)
object VouchModule {
    @Provides
    @Singleton
    fun provideVouch(@ApplicationContext context: Context): Vouch {
        return Vouch(
            context = context,
            projectId = BuildConfig.VOUCH_PROJECT_ID,
            apiKey = BuildConfig.VOUCH_API_KEY
        )
    }
}
```

### 3. Handle Errors Gracefully

Always handle validation errors without blocking the user:

```kotlin theme={null}
val result = 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")
}
```

### 4. Use StateFlow for UI Updates

Combine with StateFlow for reactive UI updates:

```kotlin theme={null}
class EmailViewModel(private val vouch: Vouch) : ViewModel() {
    private val _state = MutableStateFlow(EmailState())
    val state = _state.asStateFlow()

    fun validate(email: String) {
        viewModelScope.launch {
            _state.update { it.copy(isLoading = true) }

            val result = vouch.validate(email)

            _state.update { it.copy(
                isLoading = false,
                isValid = result.isAllowed,
                error = result.errorMessage
            )}
        }
    }
}
```

## API Reference

### Vouch

Main SDK class for email validation and fingerprinting.

```kotlin theme={null}
class Vouch(
    context: Context,
    projectId: String,
    apiKey: String,
    options: VouchOptions = VouchOptions()
) {
    suspend fun validate(email: String): ValidationResult
    suspend fun generateFingerprint(): Fingerprint
}
```

### VouchOptions

SDK configuration options.

```kotlin theme={null}
data class VouchOptions(
    val endpoint: String = "https://api.vouch.expert",
    val version: ApiVersion = ApiVersion.Latest
)
```

### ApiVersion

API version specification.

```kotlin theme={null}
sealed class ApiVersion {
    object Latest : ApiVersion()  // Unversioned endpoint
    data class Version(val number: Int) : ApiVersion()  // Versioned endpoint
}
```

### ValidationAction

Validation action enum used in toggle configuration.

```kotlin theme={null}
enum class ValidationAction {
    ALLOW,
    BLOCK,
    FLAG
}
```

### ValidationResult

Email validation response structure with convenience properties.

```kotlin theme={null}
data class ValidationResult(
    val email: String?,
    val error: String?,
    val data: ValidationData?,
    val statusCode: Int?
) {
    // Convenience properties
    val isAllowed: Boolean
    val recommendation: String?
    val signals: List<String>?
    val errorMessage: String?
}
```

### ValidationData

Validation response data — sealed class with two cases.

```kotlin theme={null}
sealed class ValidationData {
    data class Validation(
        val checks: Map<String, CheckResult>,
        val message: String?,
        val metadata: ValidationMetadata,
        val recommendation: String,  // "allow", "block", or "flag"
        val signals: List<String>
    ) : ValidationData()

    data class Error(val response: ErrorResponseData) : ValidationData()
}
```

### CheckResult

Individual check result.

```kotlin theme={null}
data class CheckResult(
    val error: String?,
    val latency: Int,
    val metadata: Map<String, JsonElement>?,
    val pass: Boolean
)
```

### ValidationMetadata

Validation metadata.

```kotlin theme={null}
data class ValidationMetadata(
    val fingerprintHash: String?,  // Device fingerprint hash
    val previousSignups: Int,  // Previous signups from this device
    val totalLatency: Int  // Total validation latency in ms
)
```

### DeviceData

Device fingerprint data returned in validation results.

```kotlin theme={null}
data class DeviceData(
    val emailsUsed: Int,
    val firstSeen: Int,
    val isKnownDevice: Boolean,
    val isNewEmail: Boolean,
    val lastSeen: Int?,
    val previousSignups: Int
)
```

### IPData

IP address analysis data.

```kotlin theme={null}
data class IPData(
    val ip: String,
    val isAnonymous: Boolean,  // True if VPN, Tor, or datacenter IP detected
    val isFraud: Boolean
)
```

### ValidationToggles

Toggle configuration for which validations to run.

```kotlin theme={null}
data class ValidationToggles(
    val alias: ValidationAction?,
    val catchall: ValidationAction?,
    val device: ValidationAction?,
    val disposable: ValidationAction?,
    val ip: ValidationAction?,
    val mx: ValidationAction?,
    val roleEmail: ValidationAction?,
    val smtp: ValidationAction?,
    val syntax: ValidationAction?
)
```

### ErrorResponseData

Error response from the API.

```kotlin theme={null}
data class ErrorResponseData(
    val error: String,
    val message: String
)
```

## Support

For issues and questions:

* **GitHub:** [github.com/vouch-platform/vouch-sdk](https://github.com/vouch-platform/vouch-sdk)
* **Email:** [support@vouch.expert](mailto:support@vouch.expert)
* **Documentation:** [docs.vouch.expert](https://docs.vouch.expert)
