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
- Add the SDK dependency. Add to your app's `build.gradle.kts`: `implementation("com.gateai.sdk:gateai:1.0.0")`
- Enable Play Integrity. Register your app's package name and SHA-256 signing certificate in the Google Play Console and Gate/AI Portal.
- Configure `GateAIConfiguration`. Provide the Gate/AI base URL, package name, signing certificate fingerprint, and an emulator-only dev token.
- 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-LocaleUser's language and region (e.g., "en-US", "es-MX") -
X-App-VersionApp version from AndroidManifest.xml -
X-OS-VersionAndroid OS version (e.g., "14", "13") -
X-Device-IdentifierAndroid ID (unique per-app, per-device) -
X-Device-TypeDevice manufacturer and model (e.g., "Google Pixel 8") -
X-User-StatusCustom 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.