Documentation · Android
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.github.GateAI-net:gate-android:1.2.0")`
- Enable Play Integrity. In the Google Play Console, open your app → Test and release → App integrity → Play Integrity API and link a Google Cloud project (enable the Play Integrity API on it). Copy the project's numeric project number from the Google Cloud Console "Project info" card and set it as `cloudProjectNumber` in your `GateAIConfiguration`. Then register your package name and SHA-256 signing certificate in the Gate/AI Portal gate settings.
- Grant Gate/AI verification access. In that same Google Cloud project: IAM & Admin → IAM → Grant access → add `[email protected]` with the role "Service Usage Consumer". Gate/AI uses this to verify your app's integrity tokens with Google — no keys are exchanged and you can revoke it anytime.
- Configure `GateAIConfiguration`. Provide the Gate/AI base URL, package name, and signing certificate fingerprint. For debug builds, configure a development token using the setup below.
- 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. Requires Android API 24+ and Java 17+.
JitPack release
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
}
// app/build.gradle.kts
dependencies {
implementation("com.github.GateAI-net:gate-android:1.2.0")
}
Option 2: Local development
// settings.gradle.kts
includeBuild("/path/to/GateAI/sdks/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.
Always set cloudProjectNumber
Google requires the cloud project number for any build that wasn't installed from the Play Store — which includes every development build you run from Android Studio or install with `adb`. Without it, those builds fail with Integrity error -16 before any network request. It's harmless for Play Store installs, so set it unconditionally.
Where to find it: Google Cloud Console → the project linked to Play Integrity in your Play Console → "Project info" card → Project number (the numeric value, not the project ID string).
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.in.gate-ai.net",
packageName = packageName,
signingCertSha256 = "AA:BB:CC:DD:...", // Your SHA-256 fingerprint
cloudProjectNumber = 123456789012L, // Google Cloud project number (Play Integrity)
logLevel = if (BuildConfig.DEBUG)
GateAIConfiguration.LogLevel.DEBUG
else
GateAIConfiguration.LogLevel.INFO
)
gateAIClient = GateAIClient.create(this, configuration)
gateAIClient.userStatus = "premium" // Optional analytics
}
}
Set up a development token for the Android emulator
Use a development token when testing in an Android emulator. This applies to native Android, React Native, Capacitor, and Flutter apps. The development token lets your emulator app authenticate with Gate/AI without using Google Play Integrity.
Follow the steps below, then rebuild and reinstall your debug app. The SDK discovers the token automatically, so initialize your client normally without passing the token in Kotlin, JavaScript, or Dart. Requires Android SDK 1.2.0+; included in the React Native, Capacitor, and Flutter SDKs.
- Create a development token for your gate in the Gate/AI portal.
-
Set
GATE_AI_DEV_TOKENin the Gradle build environment, or creategateai.local.propertiesbeside the host app’s settings.gradle or settings.gradle.kts. For framework apps this is usually the android/ directory. -
Add
gateai.local.propertiesto your .gitignore. In the local file, setGATE_AI_DEV_TOKEN=your-tokenwithout quotes. An environment variable takes precedence, including an explicitly empty value. - Add the matching snippet below to the host app module’s build file. Put Kotlin imports at the top of the file and the configuration after plugins. Merge the android / buildTypes blocks with your existing configuration. Keep the resource in the debug build type.
- Sync Gradle, then rebuild and reinstall the debug app. Repeat this after changing the token.
Kotlin DSL · app/build.gradle.kts
// app/build.gradle.kts — put this import at the top of the file
import java.util.Properties
// Place the following after your existing plugins { ... } block.
val gateAILocalProperties = Properties().apply {
val localFile = rootProject.file("gateai.local.properties")
if (localFile.exists()) localFile.inputStream().use { load(it) }
}
val gateAIDevToken = providers.environmentVariable("GATE_AI_DEV_TOKEN")
.orElse(gateAILocalProperties.getProperty("GATE_AI_DEV_TOKEN", ""))
android {
buildTypes {
getByName("debug") {
resValue("string", "gate_ai_dev_token", gateAIDevToken.get())
}
}
}
Groovy · app/build.gradle
// app/build.gradle — place after plugins { ... }
def gateAILocalProperties = new Properties()
def gateAILocalFile = rootProject.file("gateai.local.properties")
if (gateAILocalFile.exists()) {
gateAILocalFile.withInputStream { gateAILocalProperties.load(it) }
}
def gateAIDevToken = providers.environmentVariable("GATE_AI_DEV_TOKEN")
.orElse(gateAILocalProperties.getProperty("GATE_AI_DEV_TOKEN", ""))
android {
buildTypes {
debug {
resValue "string", "gate_ai_dev_token", gateAIDevToken.get()
}
}
}
Android Studio must inherit the environment variable when it starts; the ignored local file is convenient when launching the IDE from the Dock or launcher. These are build-time values, unlike iOS’s Xcode launch environment.
The SDK reads gate_ai_dev_token only in debuggable apps. If no token is configured, the SDK asks Google Play Integrity to verify the app and device instead. Explicit developmentToken configuration remains compatible and takes precedence in debug apps.
The token is present in the debug APK. Keep this resource out of defaultConfig and release build types so release APKs/AABs exclude it. The SDK also ignores all development tokens in non-debuggable apps.
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.in.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, environment, and SDK version on all requests, plus optional user and feature attribution.
-
🧪 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-Device-ModelRaw hardware model (e.g., "SM-G991U") -
X-Environment"development" (debuggable build) or "production" -
X-SDK-VersionGate/AI SDK version
Developer-set headers
Optional properties on the client; only sent when you set them.
// X-User-Status: user segment or subscription tier
client.userStatus = "premium"
// X-User-Identifier: opaque account ID from your
// own system — never an email or name
client.userIdentifier = account.analyticsId
// X-App-Feature: which in-app feature is making
// AI requests, for per-feature cost attribution
client.appFeature = "chat"
// X-User-Tier: the user's plan tier — per-tier usage
// limits configured for this gate match it exactly
client.userTier = "pro"
Country-level geography is derived server-side from the request's network edge — nothing to configure and no location permission involved.
Usage limits & quotas
Gates can enforce per-device budgets over daily, calendar-month, rolling 30-day, and billing-cycle windows. The SDK reports the user's renewal day and surfaces remaining quota so you can render limit UI, and per-tier limits key off the client's userTier value — and make hitting a cap your upgrade moment.
Billing-cycle windows
To align a device's budget with the user's subscription month, report their renewal day-of-month (from Play Billing or RevenueCat). Sent as the X-Quota-Anchor-Day header; days 29–31 clamp to short months automatically. Requires SDK 1.1.0+.
// Day-of-month (1–31) the user's subscription renews
client.quotaAnchorDay = renewalDayOfMonth
Reading remaining quota
Responses include the remaining budget for the tightest configured window:
-
X-Quota-Requests-RemainingwithX-Quota-Requests-Limitfor rendering usage meters -
X-Quota-Requests-ResetISO8601 date the request budget resets -
X-Quota-Tokens-RemainingwithX-Quota-Tokens-Limit -
X-Quota-Tokens-ResetISO8601 date the token budget resets
When a limit is hit, the 429 body names the window and reset date so your app can show "resets in N days":
{ "error": "rate_limited",
"code": "device_monthly_requests_exceeded",
"window": "monthly", "limit": 200, "used": 200,
"resets_at": "2026-10-01T00:00:00.000Z" }
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.
"Integrity API error (-16): The provided cloud project number is invalid"
Your build wasn't installed from the Play Store (development builds via Android Studio or `adb` never are), so Google requires `cloudProjectNumber` in `GateAIConfiguration`. Use the numeric project number from the Google Cloud project linked to Play Integrity in your Play Console. If the number is set and the error persists, confirm the Play Integrity API is enabled on that cloud project.
Testing on emulator
Use a development token when testing in an Android emulator. Follow the development token setup above, then rebuild and reinstall the debug app. The development token lets your emulator app authenticate with Gate/AI without using Google Play Integrity.
"401 Unauthorized"
Check that your Gate/AI base URL is correct and your device clock is synchronized. The SDK automatically handles DPoP nonce challenges.