Android SDK

Gate/AI for Android

Integrate the Gate/AI Kotlin SDK to manage Android Keystore keys with StrongBox, Play Integrity attestation, short-lived access tokens, and DPoP proofs for every proxied API call.

Quick start checklist

  1. Add the SDK dependency. Add to your app's `build.gradle.kts`: `implementation("com.gateai.sdk:gateai:1.0.0")`
  2. Enable Play Integrity. Register your app's package name and SHA-256 signing certificate in the Google Play Console and Gate/AI Portal.
  3. Configure `GateAIConfiguration`. Provide the Gate/AI base URL, package name, signing certificate fingerprint, and an emulator-only dev token.
  4. Use `GateAIClient` for requests. Call `performProxyRequest()` or `authorizationHeaders()` so every proxied API call carries DPoP proofs.

Installation

Add the Gate/AI SDK to your Android project using Gradle.

Option 1: Maven Central (Recommended)

// app/build.gradle.kts
dependencies {
    implementation("com.gateai.sdk:gateai:1.0.0")
}

Option 2: Local development

// settings.gradle.kts
includeBuild("../gate-android")

// app/build.gradle.kts
dependencies {
    implementation("com.gateai.sdk:gateai")
}

Configure the client

Initialize the Gate/AI client in your Application class with your tenant configuration.

Get your SHA-256 fingerprint

# Debug keystore
keytool -list -v -keystore ~/.android/debug.keystore \\
  -alias androiddebugkey \\
  -storepass android -keypass android | grep SHA256

# Release keystore
keytool -list -v -keystore /path/to/release.keystore \\
  -alias your-key-alias

Initialize in your Application class

class MyApplication : Application() {
    lateinit var gateAIClient: GateAIClient
        private set

    override fun onCreate() {
        super.onCreate()

        val configuration = GateAIConfiguration(
            baseUrl = "https://your-team.us01.gate-ai.net",
            packageName = packageName,
            signingCertSha256 = "AA:BB:CC:DD:...", // Your SHA-256 fingerprint
            developmentToken = if (BuildConfig.DEBUG) BuildConfig.DEV_TOKEN else null,
            logLevel = if (BuildConfig.DEBUG) 
                GateAIConfiguration.LogLevel.DEBUG 
            else 
                GateAIConfiguration.LogLevel.INFO
        )

        gateAIClient = GateAIClient.create(this, configuration)
        gateAIClient.userStatus = "premium" // Optional analytics
    }
}

Configure BuildConfig fields

// app/build.gradle.kts
android {
    defaultConfig {
        buildConfigField("String", "DEV_TOKEN", "\\"\\"")
    }

    buildTypes {
        debug {
            // For emulator testing (optional)
            buildConfigField("String", "DEV_TOKEN", "\\"your-dev-token\\"")
        }
    }
}

Make proxied requests

Use `performProxyRequest()` for complete request handling with automatic DPoP nonce retry.

class MyViewModel(application: Application) : AndroidViewModel(application) {
    private val gateAI = (application as MyApplication).gateAIClient

    fun callOpenAI() {
        viewModelScope.launch {
            try {
                val requestBody = """
                {
                    "model": "gpt-4o",
                    "messages": [
                        {"role": "user", "content": "Say hello from Gate/AI"}
                    ]
                }
                """.trimIndent()

                val response = gateAI.performProxyRequest(
                    path = "openai/chat/completions",
                    method = HttpMethod.POST,
                    body = requestBody.toByteArray(),
                    additionalHeaders = mapOf("Content-Type" to "application/json")
                )

                if (response.status == 200) {
                    val result = response.body
                    // Process response
                }
            } catch (e: GateApiException) {
                // Handle HTTP errors (400, 401, etc.)
                Log.e("GateAI", "Error: ${e.statusCode} - ${e.body}")
            } catch (e: Exception) {
                // Handle other errors
                Log.e("GateAI", "Error", e)
            }
        }
    }
}

Manual request integration

Use a custom networking stack? Pull headers from the client and handle nonce challenges manually.

// Get authorization headers
val headers = gateAI.authorizationHeaders(
    path = "anthropic/v1/messages",
    method = HttpMethod.POST
)

// Use with your HTTP client
val request = Request.Builder()
    .url("https://your-team.us01.gate-ai.net/anthropic/v1/messages")
    .post(requestBody)
    .apply {
        headers.forEach { (key, value) ->
            addHeader(key, value)
        }
        addHeader("Content-Type", "application/json")
    }
    .build()

val response = httpClient.newCall(request).execute()

// Handle DPoP nonce challenge (401)
if (response.code == 401) {
    val nonce = gateAI.extractDPoPNonce(response.headers.toMultimap())
    if (nonce != null) {
        // Retry with nonce
        val retryHeaders = gateAI.authorizationHeaders(
            path = "anthropic/v1/messages",
            method = HttpMethod.POST,
            nonce = nonce
        )
        // Make request again with retryHeaders
    }
}

SDK features

  • ๐Ÿ” Hardware-backed keys

    P-256 ECDSA keys in Android Keystore with StrongBox preference on supported devices (Android 9+).

  • ๐Ÿ“ฑ Play Integrity attestation

    Automatic device verification using Google Play Integrity API with MEETS_DEVICE_INTEGRITY minimum.

  • โ™ป๏ธ Automatic token refresh

    Tokens cached in-memory and refreshed 60 seconds before expiry with mutex-based thread safety.

  • ๐Ÿ”„ DPoP nonce retry

    Automatic handling of 401 DPoP-Nonce challenges with transparent request retry.

  • ๐Ÿ“Š Analytics headers

    Automatic inclusion of device info, app version, OS version, locale, and custom user status on all requests.

  • ๐Ÿงช Development token flow

    Test on emulators using development tokens when Play Integrity is unavailable.

Analytics headers

The SDK automatically includes analytics headers on all requests for usage tracking and insights.

Automatic headers

  • X-Client-Locale User's language and region (e.g., "en-US", "es-MX")
  • X-App-Version App version from AndroidManifest.xml
  • X-OS-Version Android OS version (e.g., "14", "13")
  • X-Device-Identifier Android ID (unique per-app, per-device)
  • X-Device-Type Device manufacturer and model (e.g., "Google Pixel 8")
  • X-User-Status Custom user segment (set via `client.userStatus`)

API reference

`GateAIClient.create()`

Factory method to create a configured client instance.

fun create(
    context: Context,
    configuration: GateAIConfiguration,
    logger: GateLogger = AndroidGateLogger()
): GateAIClient

`performProxyRequest()`

Make an authenticated request with automatic DPoP nonce retry. Returns raw status, headers, and body.

suspend fun performProxyRequest(
    path: String,
    method: HttpMethod,
    body: ByteArray? = null,
    additionalHeaders: Map = emptyMap()
): RawResponse

`authorizationHeaders()`

Get Authorization, DPoP, and analytics headers for manual request construction.

suspend fun authorizationHeaders(
    path: String,
    method: HttpMethod,
    nonce: String? = null
): Map

`currentAccessToken()`

Get the current cached access token, or null if no valid token is available.

suspend fun currentAccessToken(): String?

`clearCachedState()`

Force re-authentication by clearing the cached token state.

fun clearCachedState()

Error handling

The SDK throws structured exceptions for different error scenarios.

try {
    val response = gateAI.performProxyRequest(...)
} catch (e: GateApiException) {
    // HTTP errors (400, 401, 403, 429, 500, etc.)
    when (e.statusCode) {
        401 -> Log.e("Auth", "Unauthorized: ${e.body}")
        403 -> Log.e("Auth", "Forbidden: ${e.body}")
        429 -> Log.e("RateLimit", "Rate limited: ${e.body}")
        else -> Log.e("API", "Error ${e.statusCode}: ${e.body}")
    }
} catch (e: Exception) {
    // Network errors, timeouts, etc.
    Log.e("Network", "Request failed", e)
}

Requirements

  • Min SDK: Android 7.0 (API 24)
  • Target SDK: Android 14 (API 34)
  • Kotlin: 2.1.0+
  • Java: 17
  • Play Integrity: Required for production (development token for emulators)

Troubleshooting

"signingCertSha256 must be a hex SHA-256 fingerprint"

Get your certificate fingerprint using `keytool` and ensure it's in colon-delimited format (AA:BB:CC:...).

"Play Integrity API Error"

Ensure Play Integrity is enabled in Google Play Console and your package name + certificate are registered in the Gate/AI Portal.

Testing on emulator

Use a development token from the Gate/AI Portal and set it in your debug build configuration.

"401 Unauthorized"

Check that your Gate/AI base URL is correct and your device clock is synchronized. The SDK automatically handles DPoP nonce challenges.