Kotlin
The Kotlin SDK is a modern, coroutine-first client emitted from the same GlottoIR as every other
target. It leans on Kotlin’s defaults — primary-constructor config with default args, nullable
String? params — and emits KDoc from the operation prose in the spec.
Quickstart
implementation("com.your-org:petstore:1.0.0")
import com.your_org.petstore.Client
import kotlinx.coroutines.runBlocking
fun main() = runBlocking {
val client = Client(token = System.getenv("PETSTORE_TOKEN"))
// methods are suspend functions
val pet = client.pets.createPet(NewPet(name = "Rex"))
// paginated methods are Flows
client.pets.listPets().collect { println(it.name) }
}
Resource sub-clients
Operations hang off resource accessors — client.pets.get(id), client.pets.photos.add(...) —
rather than a flat method list, the same service-accessor shape the other engines emit.
Coroutines
Methods are suspend functions (client.pets.get(id) from a coroutine), with streaming and
pagination surfaced as a Flow, so the SDK composes with structured concurrency rather than
blocking a thread. Cancelling the calling coroutine aborts an active buffered HTTP request,
including response-body reads, on JVM and Android. Cancellation propagates as
CancellationException and is not retried. The Multiplatform client uses Ktor’s coroutine
cancellation support.
Typed errors
Non-2xx responses throw ApiError, a RuntimeException carrying the parsed, typed error body —
Kotlin has no checked exceptions, so there is nothing to wrap. Discriminated-union bodies resolve
to the right sealed-type variant. See Errors.
Pagination
Paginated list methods expose a Flow that walks every page as you collect it, advancing the
cursor for you. See Pagination.
Retries & backoff
Transient failures (5xx, 429, transport errors) retry with exponential backoff and jitter,
configurable per client. See Retries & timeouts, Streaming,
and Authentication.
Android-native
Opt-in. The Android target is compile-verified against a real Android SDK by the scheduled
mobile-compileCI lane — the emitted library (including its OkHttp transport) builds with the real Android Gradle Plugin. The plain-JVM Kotlin SDK above is the default and is unaffected.
Opt in with targets.kotlin.android: true in your glotto.yml to emit an Android library
(com.android.library) instead of the plain-JVM package — the same native-mobile integration the
React Native SDK ships, adapted to Kotlin:
targets:
kotlin:
android: true
-
Secure token storage. The client takes an optional
tokenStore: TokenStore?; the bearer token is read from it per request (falling back to the static token). A Keystore-backed implementation is generated for you —androidKeystoreTokenStore(context)encrypts values with an AES-256-GCM key held in the Android Keystore (no third-party crypto dependency). -
Network-state-aware retries. Pass a
ConnectivityMonitorand the retry loop awaits the backoff and re-checks while the device is offline rather than burning the request; the generatedAndroidConnectivityMonitoris backed by the systemConnectivityManager. -
Lifecycle-aware backoff. Pass an
AppLifecycleand the backoff pauses (no retry attempt consumed) while the app is backgrounded; the generatedAndroidAppLifecycleis backed byProcessLifecycleOwner. -
OkHttp transport. The plain-JVM SDK’s HTTP transport is the JDK’s
java.net.http.HttpClient, which is absent fromandroid.jar— so the Android variant emits its entire transport (requests, retries, streaming, file upload, OAuth token exchange) on OkHttp instead, preserving the same retry/Retry-After/telemetry/idempotency behavior. OkHttp is added as a dependency automatically.
The emitted build.gradle.kts carries the android {} block (with the Java/Kotlin JVM target pinned
to 17) and the androidx.lifecycle / okhttp dependencies, and the
AndroidManifest.xml declares the INTERNET and ACCESS_NETWORK_STATE permissions. The interfaces
(TokenStore, ConnectivityMonitor, AppLifecycle) are plain Kotlin, so you can supply your own
implementations or the bundled androidx-backed ones.
Biometric-gated storage
Opt-in, experimental.
targets.kotlin.androidBiometric: truerequirestargets.kotlin.android: trueand is compile-verified on the scheduledmobile-compilelane. The plain Keystore store above stays the default and byte-identical.
For credentials that must be released only after the user authenticates, add
targets.kotlin.androidBiometric: true:
targets:
kotlin:
android: true
androidBiometric: true
This additionally emits a BiometricKeystoreTokenStore (via
androidBiometricKeystoreTokenStore(activity, promptInfo)) whose AES-256-GCM key is created with
setUserAuthenticationRequired(true), so every read and write is authorized through a BiometricPrompt
(class-3 / STRONG biometrics) bound to the crypto operation — the plain androidKeystoreTokenStore above
stays available. The emitted build.gradle.kts adds the androidx.biometric dependency and the
AndroidManifest.xml declares the USE_BIOMETRIC permission.
Unknown response fields
Your API can add a response field without it being a breaking change — but a generated data class has no property to put it in, and Gson discards what it does not recognize. The generated models keep it instead: a field the SDK wasn’t generated from is retained on decode, readable through an accessor, and written back out when the model is re-serialized.
val pet = client.pets.createPet(body)
// A field your API started returning after this SDK was generated.
val species = pet.extraFields()["species"]
// Re-encoding preserves it — a read-modify-write never silently drops it.
val json = client.gson.toJson(pet)
extraFields() returns a read-only Map<String, JsonElement>, so nested objects and arrays
survive intact, and retention is recursive.
The Multiplatform target carries the same guarantee through kotlinx.serialization rather than
Gson, so a commonMain consumer reads the same accessor. Your existing construction still
compiles — the property is a trailing parameter with a default.
The retained fields are read-only by design. To send a field your spec doesn’t model yet, use the per-call extra-body escape hatch rather than writing to the retained bag.
Kotlin Multiplatform (preview)
Opt-in, phased. The entire shared client now lives in
commonMainon a Ktor transport — the model layer (kotlinx.serialization, including discriminated unions via a polymorphic serializer), theClient+ retry loop, synchronous webhook verification (pure-Kotlin crypto), and the OAuth/PKCE helpers are all platform-agnostic and gson-free, compile-verified on a real toolchain. Every API shape the standard Kotlin SDK supports now also emits under the multiplatform target. Landed: the iOS target (iosX64/iosArm64/iosSimulatorArm64on the Ktor Darwin/NSURLSession engine, compile-verified on a macOS/Xcode CI runner) including an iOS Keychain-backed secure token store, and the Android target —multiplatformnow composes withandroid(a declaredandroidTarget()on the OkHttp engine + an AndroidKeyStore secure token store), plus the opt-in JS / Wasm / watchOS / tvOS targets viatargets.kotlin.multiplatformTargets.
Opt in with targets.kotlin.multiplatform: true to emit a Kotlin Multiplatform project instead of
the single-module JVM library:
targets:
kotlin:
multiplatform: true
The engine emits a kotlin("multiplatform") build.gradle.kts with jvm() and the iOS targets
(iosX64 / iosArm64 / iosSimulatorArm64), plus a shared commonMain source set holding the whole
portable client: the kotlinx.serialization models (discriminated unions become a sealed interface with a
polymorphic KSerializer that dispatches on the discriminator), the Client + retry loop on a Ktor
HttpClient (the JVM target binds the OkHttp engine, iOS binds Darwin/NSURLSession;
java.net.http is JVM-only and absent from Kotlin/Native), pure-Kotlin SHA-256/HMAC so webhook
verification stays synchronous on every platform, and the OAuth/PKCE helpers (a suspend Ktor token
exchange + a secure-random expect/actual for the PKCE verifier). The remaining java.* (time, UUID,
env, URL-encoding) is handled by kotlin.time / kotlin.uuid / a common encoder plus a small
expect/actual shim (JVM-backed by System/SecureRandom, iOS by posix/Foundation) — so the
emitted client is gson-free and each platform source set carries only its HTTP engine + the platform
actuals. The default (non-multiplatform) JVM SDK is unchanged. The iOS Kotlin/Native build is
compile-verified on a scheduled macOS/Xcode CI runner. For secure credentials, the client takes an
optional tokenStore: TokenStore? read per request (falling back to the static token); iOS ships a
KeychainTokenStore backed by the system Keychain, and you can supply your own TokenStore on any
platform. Setting targets.kotlin.android: true alongside multiplatform adds a declared
androidTarget() (the OkHttp Ktor engine on androidMain + an AndroidKeyStore-backed KeystoreTokenStore)
to the same KMP build — the android target is configured only on a machine with the Android SDK, so the
jvm()/iOS build stays buildable without it.
Because the commonMain client is fully platform-agnostic, you can opt into extra targets beyond the
default JVM/iOS(+Android) set with targets.kotlin.multiplatformTargets — any of js, wasmJs, watchos,
tvos:
targets:
kotlin:
multiplatform: true
multiplatformTargets: [js, wasmJs, watchos, tvos]
js adds a js(IR) target and wasmJs a Kotlin/Wasm target, both on the Ktor JS engine
(browser fetch / Node) with a Platform.js.kt / Platform.wasmJs.kt actual over the standard JS APIs
(process.env, the JS Date, Web-Crypto getRandomValues); webhook verification stays synchronous there
too (the crypto is pure Kotlin). watchos / tvos add the watchOS/tvOS targets, which reuse the iOS
Darwin actuals (and Keychain) via a shared appleMain source set. Omit multiplatformTargets (or leave it
empty) and the output is exactly the JVM/iOS(+Android) project above, byte-for-byte. JS + Wasm are
compile-verified on Linux CI; the Apple targets on the macOS lane.
Publishing works the same as the single-module Kotlin SDK: the generated multiplatform build.gradle.kts
applies the com.vanniktech.maven.publish plugin, so publishing the Kotlin target (a single
publishAndReleaseToMavenCentral Gradle task) ships the root module + every per-target variant
(-jvm, -android, -iosarm64, -js, …) and the Gradle Module Metadata that lets a multiplatform
consumer resolve the right artifact per target.
Pages, request controls, and files
Paginated methods also expose manual pages, so you can inspect one response and request its successor explicitly. Request overrides apply to the initial call and to each requested next page; see Retries & timeouts for precedence and cancellation.
Binary downloads return an owned byte response with metadata, a bounded read helper, and incremental consumption. Close the response when you stop early. Your generated README demonstrates these operations with your API’s names and the language’s native calling conventions.