> For the complete documentation index, see [llms.txt](/llms.txt)

# 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

```kotlin
implementation("com.your-org:petstore:1.0.0")
```

```kotlin
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](/docs/errors).

## Pagination

Paginated list methods expose a `Flow` that walks every page as you collect it, advancing the
cursor for you. See [Pagination](/docs/pagination).

## Retries & backoff

Transient failures (`5xx`, `429`, transport errors) retry with exponential backoff and jitter,
configurable per client. See [Retries & timeouts](/docs/retries), [Streaming](/docs/streaming),
and [Authentication](/docs/authentication).

## Android-native

> **Opt-in.** The Android target is compile-verified against a real Android SDK by the scheduled
> `mobile-compile` CI 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:

```yaml
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 `ConnectivityMonitor` and the retry loop awaits the
  backoff and re-checks while the device is offline rather than burning the request; the generated
  `AndroidConnectivityMonitor` is backed by the system `ConnectivityManager`.
- **Lifecycle-aware backoff.** Pass an `AppLifecycle` and the backoff pauses (no retry attempt
  consumed) while the app is backgrounded; the generated `AndroidAppLifecycle` is backed by
  `ProcessLifecycleOwner`.

- **OkHttp transport.** The plain-JVM SDK's HTTP transport is the JDK's `java.net.http.HttpClient`,
  which is absent from `android.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: true` requires
> `targets.kotlin.android: true` and is compile-verified on the scheduled `mobile-compile` lane.
> 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`:

```yaml
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.

```kotlin
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 `commonMain` on a **Ktor transport** —
> the model layer (`kotlinx.serialization`, including discriminated unions via a polymorphic serializer),
> the `Client` + 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`/`iosSimulatorArm64` on 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**
> — `multiplatform` now composes with `android` (a declared `androidTarget()` on the OkHttp engine + an
> AndroidKeyStore secure token store), plus the opt-in **JS / Wasm / watchOS / tvOS** targets via
> `targets.kotlin.multiplatformTargets`.

Opt in with `targets.kotlin.multiplatform: true` to emit a **Kotlin Multiplatform** project instead of
the single-module JVM library:

```yaml
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`:

```yaml
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](/docs/pagination#fetch-one-page),
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](/docs/retries) for precedence and cancellation.

[Binary downloads](/docs/file-transfers) 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.
