Browse documentation

Android SDK

The official nRouter Android SDK packaged as an AAR with coroutines support, main-thread safety, and automatic response metadata extraction.

Last updated

The official nRouter Android SDK (ai.nrouter:nrouter-sdk-android) provides idiomatic Kotlin coroutine bindings for Android applications. Built on top of the shared Kotlin core client, it delivers main-thread safety, automatic lifecycle-safe dispatching, and response metadata extraction while connecting to https://api.nrouter.ai/v1.

Source Installation

Android is currently a source preview rather than a Maven Central release. Build the same-version Kotlin core and Android AAR into your local Maven cache first:

cd sdks/kotlin && ./gradlew clean check publishToMavenLocal
cd ../android && ./gradlew clean build publishToMavenLocal

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

// app/build.gradle.kts
repositories {
    mavenLocal()
    mavenCentral()
}

dependencies {
    implementation("ai.nrouter:nrouter-sdk-android:3.1.2")
}

The package declares android.permission.INTERNET and bundles consumer ProGuard rules automatically, so no additional R8 configuration is required. Minimum supported Android version is API 21 (minSdk = 21).

Setup & Authentication

Because System.getenv returns null on Android devices, do not rely on environment variables at runtime. Pass your virtual API key directly to the factory method:

import ai.nrouter.sdk.android.NRouterAndroid

// In production, pass a short-lived key minted by your backend:
val client = NRouterAndroid.create(context, "sk-nrouter-your-api-key-here")

For development and local unit testing via the command line, export the environment variable:

export NROUTER_API_KEY="sk-nrouter-your-api-key-here"

Requests target https://api.nrouter.ai by default.

Key Safety in Mobile Applications

Anything bundled inside an APK or Android app bundle (BuildConfig, strings.xml, or AndroidManifest meta-data) can be extracted by users. Never hardcode long-lived production keys in client binaries. For production deployments, your backend service should mint a short-lived virtual key and deliver it to authenticated clients.

For prototyping or internal builds, manifest configuration is supported:

<application>
    <meta-data android:name="ai.nrouter.sdk.API_KEY" android:value="sk-nrouter-..." />
</application>
val client = NRouterAndroid.create(context)

Quickstart

Make asynchronous chat completions from an AndroidViewModel or coroutine scope:

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import ai.nrouter.sdk.android.NRouterAndroid
import kotlinx.coroutines.launch
import org.json.JSONObject

class ChatViewModel(app: Application) : AndroidViewModel(app) {
    private val client = NRouterAndroid.create(app, apiKey = "sk-nrouter-your-api-key-here")

    fun sendMessage(prompt: String) = viewModelScope.launch {
        // SDK calls automatically switch to Dispatchers.IO
        val result = client.chatCompletions(
            JSONObject()
                .put("model", "gpt-5.4-mini")
                .put("messages", listOf(mapOf("role" to "user", "content" to prompt)))
        )

        val messageContent = result.body
            .getJSONArray("choices")
            .getJSONObject(0)
            .getJSONObject("message")
            .getString("content")

        println("Response: $messageContent")

        // Inspect metadata
        if (result.meta.isPriced) {
            println("Cost: $${result.meta.cost}")
        }
    }
}

Streaming

The Android SDK shares the Kotlin core's cold, cancellable Flow streaming APIs. Each chunk delivers the text delta along with the raw frame and gateway metadata:

import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import org.json.JSONObject

viewModelScope.launch {
    client.messagesStream(
        JSONObject()
            .put("model", "claude-haiku-4-5-20251001")
            .put("max_tokens", 256)
            .put("messages", listOf(mapOf("role" to "user", "content" to "Hello!")))
    ).collect { chunk ->
        print(chunk.delta)
    }
}

Cancelling the coroutine job cancels the underlying HTTP call immediately.

Error Handling

All gateway refusals map to typed exceptions under NRouterError:

import ai.nrouter.sdk.NRouterError

try {
    val response = client.chatCompletions(requestBody)
} catch (e: NRouterError.GuardrailBlocked) {
    // Blocked by configured guardrail policy
    println("Request blocked by guardrails: ${e.message}")
} catch (e: NRouterError.Credit) {
    // Insufficient credits or budget exhausted
    println("Credit check failed: ${e.message}")
} catch (e: NRouterError.RateLimit) {
    // Rate limit or TPM limit exceeded
    if (e.isRetryable) {
        println("Rate limit reached, retryable after backoff")
    }
}
ClassHTTP StatusDescription
NRouterError.Request400Invalid request format or parameters
NRouterError.GuardrailBlocked400Blocked by server-side guardrail policies
NRouterError.Authentication401Invalid or revoked API key
NRouterError.Credit402Insufficient balance or budget ceiling hit
NRouterError.NotFound404Requested model not found or inactive
NRouterError.RateLimit429Rate limit or TPM limit exceeded
NRouterError.Service503Upstream service or credit check unavailable
NRouterError.TransportNetwork connection failure

Response Metadata & Cost Tracking

Every response provides parsed x-nr-* gateway headers via result.meta:

  • requestId (x-nr-request-id): Unique request identifier.
  • latencyMs (x-nr-latency-ms): Milliseconds measured from edge arrival to response headers ready.
  • cost (x-nr-request-cost): Exact USD cost of the request.
  • costStatus (x-nr-cost-status): exact or unpriced.
  • inputTokens / outputTokens: Token usage counts.

Source & Repository

The source code and runnable demonstrations are available on GitHub:

Was this page helpful?