diff --git a/mintlify-codegen/android-reference/README.md b/mintlify-codegen/android-reference/README.md
index 11d927a9c..db35401ae 100644
--- a/mintlify-codegen/android-reference/README.md
+++ b/mintlify-codegen/android-reference/README.md
@@ -1,52 +1,46 @@
-# Android Reference Page Generator (SCAFFOLD)
+# Android Reference Page Generator
-The Android counterpart of [`../ios-reference/`](../ios-reference/README.md). When
-complete, it will regenerate `mintlify-docs/mobile-sdks/android/reference/*.mdx`
-automatically from the Android SDK's Dokka output.
+The Android counterpart of [`../ios-reference/`](../ios-reference/README.md). It
+regenerates `mintlify-docs/mobile-sdks/android/reference/*.mdx` from the Seam
+Android SDK's public Kotlin sources. The generator **owns** these pages — it
+replaces them wholesale on each run, so the SDK's public KDoc is the source of
+truth (the same model as the iOS reference).
-## Current Status: Not Implemented — Hand-Authored Pages in Place
-
-> The Android reference MDX pages are currently **hand-authored** (DOC-231, #1155)
-> from the public Kotlin API in the `phone` repo
-> (`seam-phone-android/seam-phone-sdk-android/core/src/main/java/co/seam/core/api/*.kt`).
-> `generate.ts` is a **no-op stub**: it accepts `--archive` and exits 0 without
-> writing, so the sync workflow stays a safe no-op and never overwrites those
-> pages until the real generator lands.
-
-## Intended Pipeline
+## Pipeline
```
-phone/seam-phone-android/ ← Kotlin SDK (Dokka 1.9.10 configured)
-
- ./gradlew dokkaHtmlMultiModule ← aggregates each module's dokkaHtmlPartial
- ↓
-build/dokka/htmlMultiModule/ ← Dokka output
+phone/seam-phone-android/seam-phone-sdk-android/ ← public Kotlin sources + KDoc
tsx mintlify-codegen/android-reference/generate.ts \
- --archive build/dokka/htmlMultiModule
+ --source /path/to/seam-phone-sdk-android
↓
-mintlify-docs/mobile-sdks/android/reference/*.mdx
+mintlify-docs/mobile-sdks/android/reference/*.mdx (+ index.mdx)
```
-The phone-side workflow is `seamapi/phone`
-`.github/workflows/seam-phone-android-documentation.yml` — currently
-`workflow_dispatch`-only. Enable its `push:` trigger once this generator works.
-The docs-side automerge path (`mintlify-docs/mobile-sdks/**`) and the
-`seam-ci-bot[bot]` author gate already cover Android, so no `automerge.yml`
-change is needed.
+The phone-side workflow `seamapi/phone`
+`.github/workflows/seam-phone-android-documentation.yml` checks out both repos,
+runs this generator against the checked-out Kotlin sources, and opens an
+auto-merging PR authored by `seam-ci-bot[bot]`. The docs-side automerge path
+(`mintlify-docs/mobile-sdks/**`) and the `seam-ci-bot[bot]` author gate already
+cover Android, so no `automerge.yml` change is needed.
-## Key Design Decision Before Implementing
+## Why parse sources, not Dokka
Unlike Swift-DocC (which emits clean per-symbol render JSON that
-`ios-reference/generate.ts` parses), Dokka 1.9 has no first-class JSON output.
-Pick one source to parse and design against a **real Dokka build's output**:
-
-- **`dokkaHtmlMultiModule`** — HTML; richest but hardest to parse cleanly.
-- **`dokkaGfmMultiModule`** (GFM plugin) — Markdown; closest to MDX, likely the
- least work.
-- **Kotlin public sources** — parse `co.seam.core.api` declarations directly,
- skipping Dokka entirely.
-
-Then mirror `../ios-reference/generate.ts` for the per-type page + index
-rendering, and match the structure of the existing hand-authored pages so the
-generator's first run is a clean diff rather than a rewrite.
+`ios-reference/generate.ts` parses), Dokka 1.9 has no first-class structured
+output — only HTML, which is fragile to parse. The Seam Android public API is a
+small, well-KDoc'd surface, so the generator parses the `.kt` sources directly
+(no Gradle/Dokka build needed in CI). The `PAGES` manifest in `generate.ts` maps
+each public type to its source file and output slug — edit a row to add or
+retarget a page.
+
+## What it produces vs. what it can't
+
+Each page gets: frontmatter, an `## Overview` (declaration + class KDoc),
+and member sections (properties / cases / methods) with signatures, KDoc prose,
+and `@param`/`@return`/`@throws` tables. KDoc `[Ref]` links become inline code.
+
+It is **bounded by the KDoc**: curated extras that aren't in the source (friendly
+provider-name tables, conceptual sections, hand-written examples) are not
+reproduced, and any stale KDoc (e.g. examples referencing renamed symbols) is
+emitted verbatim — fix those upstream in the SDK's KDoc.
diff --git a/mintlify-codegen/android-reference/generate.ts b/mintlify-codegen/android-reference/generate.ts
index bd293e832..a8c8b6129 100644
--- a/mintlify-codegen/android-reference/generate.ts
+++ b/mintlify-codegen/android-reference/generate.ts
@@ -1,52 +1,734 @@
/* eslint-disable no-console */
/**
- * Android Reference Page Generator — SCAFFOLD (not yet implemented)
+ * Android Reference Page Generator
*
- * The Android counterpart of `../ios-reference/generate.ts`. When complete it
- * will transform Dokka output (from `./gradlew dokkaHtmlMultiModule` in the
- * seamapi/phone Android SDK) into
- * `mintlify-docs/mobile-sdks/android/reference/*.mdx`.
+ * Transforms the Seam Android SDK's public Kotlin sources (KDoc) into
+ * `mintlify-docs/mobile-sdks/android/reference/*.mdx` — the Android counterpart
+ * of `../ios-reference/generate.ts`.
*
- * STATUS: stub. It parses its CLI contract and exits 0 WITHOUT writing any
- * files, so the sync workflow is a safe no-op until the real generator lands —
- * the hand-authored Android reference pages (DOC-231) are never overwritten.
+ * Dokka 1.9 has no clean per-symbol JSON (unlike Swift-DocC), so this parses the
+ * public sources directly. The PAGES manifest below maps each documented public
+ * type to its source file and output slug; edit a row to add/retarget a page.
+ * The generator OWNS these pages — it replaces them wholesale on each run, so
+ * the public KDoc in the SDK is the source of truth.
*
- * To implement, see README.md (esp. the Dokka-output design decision) and
- * mirror ../ios-reference/generate.ts.
- *
- * Usage (once implemented):
- * tsx mintlify-codegen/android-reference/generate.ts --archive /path/to/dokka/htmlMultiModule
- * DOKKA_ARCHIVE=/path/to/dokka/htmlMultiModule tsx mintlify-codegen/android-reference/generate.ts
+ * Usage:
+ * tsx mintlify-codegen/android-reference/generate.ts --source /path/to/seam-phone-sdk-android
+ * ANDROID_SDK_SOURCE=/path/to/seam-phone-sdk-android tsx .../generate.ts
*/
+import { mkdir, readFile, writeFile } from 'node:fs/promises'
+import { dirname, join } from 'node:path'
import { argv, env, exit } from 'node:process'
+import { fileURLToPath } from 'node:url'
-function resolveArchivePath(): string | undefined {
- const flagIndex = argv.indexOf('--archive')
- if (flagIndex !== -1 && argv[flagIndex + 1] != null) {
- return argv[flagIndex + 1]
+// ---------------------------------------------------------------------------
+// Manifest — public type -> source file (relative to --source root) + slug
+// ---------------------------------------------------------------------------
+
+interface PageSpec {
+ type: string
+ sourceFile: string
+ slug: string
+}
+
+const PAGES: PageSpec[] = [
+ {
+ type: 'SeamSDK',
+ sourceFile: 'core/src/main/java/co/seam/core/api/SeamSDK.kt',
+ slug: 'seam-sdk',
+ },
+ {
+ type: 'SeamCredential',
+ sourceFile: 'core/src/main/java/co/seam/core/api/SeamCredential.kt',
+ slug: 'seam-credential',
+ },
+ {
+ type: 'SeamError',
+ sourceFile: 'core/src/main/java/co/seam/core/sdkerrors/SeamError.kt',
+ slug: 'seam-error',
+ },
+ {
+ type: 'SeamCredentialError',
+ sourceFile: 'core/src/main/java/co/seam/core/sdkerrors/SeamError.kt',
+ slug: 'seam-credential-error',
+ },
+ {
+ type: 'SeamRequiredUserInteraction',
+ sourceFile: 'core/src/main/java/co/seam/core/sdkerrors/SeamError.kt',
+ slug: 'seam-required-user-interaction',
+ },
+ {
+ type: 'SeamUnlockEvent',
+ sourceFile: 'core/src/main/java/co/seam/core/events/SeamUnlockEvent.kt',
+ slug: 'seam-unlock-event',
+ },
+ {
+ type: 'UnlockProximity',
+ sourceFile: 'core/src/main/java/co/seam/core/api/UnlockProximity.kt',
+ slug: 'unlock-proximity',
+ },
+]
+
+const OUTPUT_DIR_REL = 'mobile-sdks/android/reference'
+
+// ---------------------------------------------------------------------------
+// Parsed model
+// ---------------------------------------------------------------------------
+
+type TypeKind =
+ | 'data class'
+ | 'sealed class'
+ | 'enum class'
+ | 'class'
+ | 'interface'
+ | 'object'
+
+interface Kdoc {
+ body: string
+ params: Array<{ name: string; desc: string }>
+ returns: string | null
+ throwsList: Array<{ type: string; desc: string }>
+}
+
+interface Member {
+ name: string
+ signature: string
+ doc: Kdoc
+}
+
+interface MemberGroup {
+ title: string
+ members: Member[]
+}
+
+interface ParsedType {
+ name: string
+ kind: TypeKind
+ declaration: string
+ doc: Kdoc
+ groups: MemberGroup[]
+}
+
+// ---------------------------------------------------------------------------
+// KDoc parsing
+// ---------------------------------------------------------------------------
+
+/** Strip the comment framing and leading asterisks from each KDoc line. */
+function stripKdocFraming(raw: string): string[] {
+ return raw
+ .replace(/^\s*\/\*\*/, '')
+ .replace(/\*\/\s*$/, '')
+ .split('\n')
+ .map((line) => line.replace(/^\s*\* ?/, '').replace(/^\s*$/, ''))
+}
+
+/** Convert KDoc `[Ref]` links to inline code, but never inside fenced code. */
+function convertRefs(body: string): string {
+ const segments = body.split(/(```[\s\S]*?```)/g)
+ return segments
+ .map((seg) => {
+ if (seg.startsWith('```')) return seg
+ // [Symbol] / [Symbol.member] not followed by ( → inline code.
+ return seg.replace(/\[([A-Za-z0-9_.]+)\](?!\()/g, '`$1`')
+ })
+ .join('')
+}
+
+const TAG_RE = /^@(param|return|returns|throws|see|property)\b\s*(.*)$/
+
+function parseKdoc(raw: string | null): Kdoc {
+ const empty: Kdoc = { body: '', params: [], returns: null, throwsList: [] }
+ if (raw == null) return empty
+
+ const lines = stripKdocFraming(raw)
+ const bodyLines: string[] = []
+ const params: Array<{ name: string; desc: string }> = []
+ const throwsList: Array<{ type: string; desc: string }> = []
+ let returns: string | null = null
+
+ type Cursor =
+ | { kind: 'body' }
+ | { kind: 'param'; idx: number }
+ | { kind: 'throws'; idx: number }
+ | { kind: 'returns' }
+ | { kind: 'ignore' }
+ let cursor: Cursor = { kind: 'body' }
+ let inFence = false
+
+ for (const line of lines) {
+ if (line.trim().startsWith('```')) inFence = !inFence
+
+ const tag = inFence ? null : line.match(TAG_RE)
+ if (tag != null) {
+ const kind = tag[1]
+ const rest = (tag[2] ?? '').trim()
+ if (kind === 'param' || kind === 'property') {
+ const sp = rest.indexOf(' ')
+ const name = sp === -1 ? rest : rest.slice(0, sp)
+ const desc = sp === -1 ? '' : rest.slice(sp + 1).trim()
+ params.push({ name, desc })
+ cursor = { kind: 'param', idx: params.length - 1 }
+ } else if (kind === 'return' || kind === 'returns') {
+ returns = rest
+ cursor = { kind: 'returns' }
+ } else if (kind === 'throws') {
+ const sp = rest.indexOf(' ')
+ const type = sp === -1 ? rest : rest.slice(0, sp)
+ const desc = sp === -1 ? '' : rest.slice(sp + 1).trim()
+ throwsList.push({ type, desc })
+ cursor = { kind: 'throws', idx: throwsList.length - 1 }
+ } else {
+ cursor = { kind: 'ignore' } // @see — dropped
+ }
+ continue
+ }
+
+ // Continuation of whatever tag/body we are in.
+ if (cursor.kind === 'body') {
+ bodyLines.push(line)
+ } else if (cursor.kind === 'param') {
+ const p = params[cursor.idx]
+ if (p != null && line.trim() !== '') {
+ p.desc = `${p.desc} ${line.trim()}`.trim()
+ }
+ } else if (cursor.kind === 'throws') {
+ const t = throwsList[cursor.idx]
+ if (t != null && line.trim() !== '') {
+ t.desc = `${t.desc} ${line.trim()}`.trim()
+ }
+ } else if (cursor.kind === 'returns') {
+ if (line.trim() !== '') returns = `${returns} ${line.trim()}`.trim()
+ }
+ }
+
+ const body = convertRefs(bodyLines.join('\n'))
+ .replace(/\n{3,}/g, '\n\n')
+ .trim()
+ return {
+ body,
+ params: params.map((p) => ({ name: p.name, desc: convertRefs(p.desc) })),
+ returns: returns != null ? convertRefs(returns) : null,
+ throwsList: throwsList.map((t) => ({
+ type: t.type,
+ desc: convertRefs(t.desc),
+ })),
}
- return env['DOKKA_ARCHIVE']
}
-function main(): void {
- const archivePath = resolveArchivePath()
+// ---------------------------------------------------------------------------
+// Kotlin source slicing
+// ---------------------------------------------------------------------------
+
+/** Find the KDoc block ending immediately before `index` (skipping annotations). */
+function kdocEndingBefore(text: string, index: number): string | null {
+ // The type's KDoc is the last full KDoc block ending at/before `index`, with
+ // only annotations/whitespace between it and the declaration.
+ const candidate = findKdocBlocks(text.slice(0, index)).at(-1)
+ if (candidate == null) return null
+ const between = text.slice(candidate.end, index)
+ if (!/^[\s]*(@[\w()]+[ \t]*\n?)*[\s]*$/.test(between)) return null
+ return candidate.raw
+}
+
+/** End index (exclusive) of a (possibly nested) block comment starting at `i`. */
+function endOfBlockComment(text: string, i: number): number {
+ let depth = 0
+ let j = i
+ while (j < text.length) {
+ if (text[j] === '/' && text[j + 1] === '*') {
+ depth++
+ j += 2
+ } else if (text[j] === '*' && text[j + 1] === '/') {
+ depth--
+ j += 2
+ if (depth === 0) return j
+ } else j++
+ }
+ return text.length
+}
- console.warn(
- '[android-reference] Generator not yet implemented — this is a scaffold.',
+/** End index (exclusive) of a string literal (handles `"""` and `"`) at `i`. */
+function endOfString(text: string, i: number): number {
+ if (text.slice(i, i + 3) === '"""') {
+ const e = text.indexOf('"""', i + 3)
+ return e === -1 ? text.length : e + 3
+ }
+ let j = i + 1
+ while (j < text.length) {
+ if (text[j] === '\\') j += 2
+ else if (text[j] === '"') return j + 1
+ else j++
+ }
+ return text.length
+}
+
+/** If `i` starts a comment or string, return the index just past it; else null. */
+function skipNonCode(text: string, i: number): number | null {
+ if (text[i] === '/' && text[i + 1] === '*') return endOfBlockComment(text, i)
+ if (text[i] === '/' && text[i + 1] === '/') {
+ const nl = text.indexOf('\n', i)
+ return nl === -1 ? text.length : nl
+ }
+ if (text[i] === '"') return endOfString(text, i)
+ return null
+}
+
+/** Match the closing delimiter for the opener at `open`, skipping comments/strings. */
+function matchDelimiter(text: string, open: number): number {
+ const opener = text[open]
+ const closer = opener === '(' ? ')' : opener === '{' ? '}' : '>'
+ let depth = 0
+ let i = open
+ while (i < text.length) {
+ if (i !== open) {
+ const skip = skipNonCode(text, i)
+ if (skip != null) {
+ i = skip
+ continue
+ }
+ }
+ const c = text[i]
+ if (c === opener) depth++
+ else if (c === closer) {
+ depth--
+ if (depth === 0) return i
+ }
+ i++
+ }
+ return -1
+}
+
+/** Find every top-level KDoc block, nesting-aware. */
+function findKdocBlocks(
+ text: string,
+): Array<{ start: number; end: number; raw: string }> {
+ const blocks: Array<{ start: number; end: number; raw: string }> = []
+ let i = 0
+ while (i < text.length) {
+ if (text[i] === '/' && text[i + 1] === '*' && text[i + 2] === '*') {
+ const end = endOfBlockComment(text, i)
+ blocks.push({ start: i, end, raw: text.slice(i, end) })
+ i = end
+ } else i++
+ }
+ return blocks
+}
+
+/** The first full KDoc block in `text`, or null. */
+function firstKdocBlock(text: string): string | null {
+ return findKdocBlocks(text)[0]?.raw ?? null
+}
+
+interface TypeSlice {
+ kind: TypeKind
+ declaration: string
+ doc: Kdoc
+ ctorParamsRaw: string | null
+ bodyRaw: string | null
+}
+
+function sliceType(text: string, typeName: string): TypeSlice {
+ const re = new RegExp(
+ `((?:public |private |internal |abstract |open |sealed |data |enum )*)\\b(class|interface|object)\\s+${typeName}\\b`,
)
- console.warn(
- '[android-reference] No pages written; hand-authored Android reference pages are preserved.',
+ const m = re.exec(text)
+ if (m == null || m.index == null) {
+ throw new Error(`Type "${typeName}" not found in source`)
+ }
+ const modifiers = (m[1] ?? '').trim()
+ const keyword = m[2] ?? 'class'
+ const kind: TypeKind = modifiers.includes('data')
+ ? 'data class'
+ : modifiers.includes('sealed')
+ ? 'sealed class'
+ : modifiers.includes('enum')
+ ? 'enum class'
+ : (keyword as TypeKind)
+
+ const doc = parseKdoc(kdocEndingBefore(text, m.index))
+
+ // Walk from the end of the matched declaration header to find primary-ctor
+ // parens (if any) and/or the body `{ ... }`.
+ let i = m.index + m[0].length
+ // Skip `private/internal/protected constructor`.
+ const ctorKw = /^\s*(?:private|internal|protected)?\s*constructor\b/.exec(
+ text.slice(i),
)
- if (archivePath != null) {
- console.warn(`[android-reference] Received --archive: ${archivePath}`)
+ if (ctorKw != null) i += ctorKw[0].length
+
+ let ctorParamsRaw: string | null = null
+ // Next significant char: `(` => primary ctor.
+ const afterWs = /^\s*/.exec(text.slice(i))?.[0].length ?? 0
+ if (text[i + afterWs] === '(') {
+ const open = i + afterWs
+ const close = matchDelimiter(text, open)
+ ctorParamsRaw = text.slice(open + 1, close)
+ i = close + 1
}
- console.warn(
- '[android-reference] See android-reference/README.md and ios-reference/generate.ts to implement.',
+
+ // Header runs until the body `{` or end of declaration.
+ const braceRel = text.slice(i).indexOf('{')
+ // Stop the header at a newline that is clearly past the declaration when no body.
+ const headerEnd = braceRel === -1 ? text.length : i + braceRel
+ const headerTail = text.slice(i, headerEnd).replace(/\s+/g, ' ').trim()
+
+ let bodyRaw: string | null = null
+ if (braceRel !== -1) {
+ const open = i + braceRel
+ const close = matchDelimiter(text, open)
+ bodyRaw = text.slice(open + 1, close)
+ }
+
+ // Build a clean declaration line (without embedded property KDoc).
+ let declaration: string
+ if (kind === 'data class' && ctorParamsRaw != null) {
+ const props = parseConstructorProps(ctorParamsRaw)
+ const body = props.map((p) => ` ${p.signature}`).join(',\n')
+ declaration = `${modifiers} ${keyword} ${typeName}(\n${body}\n)`.trim()
+ } else {
+ const ctor =
+ ctorParamsRaw != null
+ ? `(${ctorParamsRaw.replace(/\s+/g, ' ').trim()})`
+ : ''
+ declaration =
+ `${modifiers} ${keyword} ${typeName}${ctor}${headerTail ? ` ${headerTail}` : ''}`.trim()
+ }
+
+ return { kind, declaration, doc, ctorParamsRaw, bodyRaw }
+}
+
+// ---------------------------------------------------------------------------
+// Member extraction
+// ---------------------------------------------------------------------------
+
+/** Split on depth-0 commas, respecting () <> {} and skipping comments/strings. */
+function splitTopLevel(text: string): string[] {
+ const out: string[] = []
+ let depth = 0
+ let start = 0
+ let i = 0
+ while (i < text.length) {
+ const skip = skipNonCode(text, i)
+ if (skip != null) {
+ i = skip
+ continue
+ }
+ const c = text[i]
+ if (c === '(' || c === '<' || c === '{') depth++
+ else if (c === ')' || c === '>' || c === '}') depth--
+ else if (c === ',' && depth === 0) {
+ out.push(text.slice(start, i))
+ start = i + 1
+ }
+ i++
+ }
+ if (start < text.length) out.push(text.slice(start))
+ return out
+}
+
+/** Parse `val name: Type [= default]` segments (with optional leading KDoc). */
+function parseConstructorProps(raw: string): Member[] {
+ return splitTopLevel(raw)
+ .map((seg) => {
+ const kdocRaw = firstKdocBlock(seg)
+ const code = (kdocRaw != null ? seg.replace(kdocRaw, '') : seg).trim()
+ const decl = code.match(/\b(val|var)\s+(\w+)\s*:\s*([^=]+?)(?:\s*=.*)?$/s)
+ if (decl == null) return null
+ const keyword = decl[1]
+ const name = decl[2] ?? ''
+ const type = (decl[3] ?? '').replace(/\s+/g, ' ').trim()
+ return {
+ name,
+ signature: `${keyword} ${name}: ${type}`,
+ doc: parseKdoc(kdocRaw),
+ }
+ })
+ .filter((m): m is Member => m != null)
+}
+
+/** Collect KDoc'd members from a `{ ... }` body, given the parent kind. */
+function extractBodyMembers(body: string, parentKind: TypeKind): MemberGroup[] {
+ const funcs: Member[] = []
+ const props: Member[] = []
+ const cases: Member[] = []
+ const companion: Member[] = []
+
+ const collectInto = (text: string, isCompanion: boolean): void => {
+ for (const block of findKdocBlocks(text)) {
+ const docRaw = block.raw
+ const after = text.slice(block.end)
+ // The declaration is the first non-blank, non-annotation code line(s).
+ const code = after.replace(/^(\s*@[\w()]+\s*)*/, '').replace(/^\s+/, '')
+ const doc = parseKdoc(docRaw)
+
+ const fn = code.match(
+ /^(?:public |private |internal |override )*(suspend\s+)?fun\s+(\w+)\s*\(/,
+ )
+ const nested = code.match(
+ /^(?:public |private |internal )*(data\s+)?(class|object)\s+(\w+)/,
+ )
+ const prop = code.match(
+ /^(?:public |private |internal )*(val|var)\s+(\w+)/,
+ )
+ const enumConst = code.match(/^([A-Z][A-Z0-9_]*)\s*[,;(]/)
+
+ if (fn != null) {
+ const isSuspend = fn[1] != null
+ const name = fn[2] ?? ''
+ const sig = extractFunctionSignature(code)
+ if (isCompanion) companion.push({ name, signature: sig, doc })
+ else {
+ funcs.push({
+ name: `${isSuspend ? 'suspend ' : ''}${name}`,
+ signature: sig,
+ doc,
+ })
+ }
+ } else if (nested != null) {
+ const name = nested[3] ?? ''
+ cases.push({ name, signature: extractNestedSignature(code), doc })
+ } else if (parentKind === 'enum class' && enumConst != null) {
+ cases.push({ name: enumConst[1] ?? '', signature: '', doc })
+ } else if (prop != null) {
+ const name = prop[2] ?? ''
+ props.push({ name, signature: extractPropertySignature(code), doc })
+ }
+ }
+ }
+
+ // Pull out a companion object body, collect it separately, then the rest.
+ const compMatch = body.match(/companion\s+object\s*\{/)
+ if (compMatch != null && compMatch.index != null) {
+ const open = body.indexOf('{', compMatch.index)
+ const close = matchDelimiter(body, open)
+ collectInto(body.slice(open + 1, close), true)
+ collectInto(body.slice(0, compMatch.index) + body.slice(close + 1), false)
+ } else {
+ collectInto(body, false)
+ }
+
+ const groups: MemberGroup[] = []
+ if (companion.length > 0) {
+ groups.push({ title: 'Companion object methods', members: companion })
+ }
+ if (props.length > 0) groups.push({ title: 'Properties', members: props })
+ if (cases.length > 0) {
+ groups.push({
+ title: parentKind === 'enum class' ? 'Values' : 'Cases',
+ members: cases,
+ })
+ }
+ if (funcs.length > 0) groups.push({ title: 'Methods', members: funcs })
+ return groups
+}
+
+/** `fun name(...): Ret` — declaration up to the body `{` or `=`. */
+function extractFunctionSignature(code: string): string {
+ const open = code.indexOf('(')
+ const close = matchDelimiter(code, open)
+ if (open === -1 || close === -1) return code.split('\n')[0]?.trim() ?? code
+ const head = code.slice(0, open).replace(/\s+/g, ' ').trim()
+ const params = code
+ .slice(open + 1, close)
+ .replace(/\s+/g, ' ')
+ .trim()
+ let tail = code.slice(close + 1)
+ const stop = tail.search(/[{=]/)
+ tail = (stop === -1 ? tail : tail.slice(0, stop)).replace(/\s+/g, ' ').trim()
+ return `${head}(${params})${tail ? ` ${tail}` : ''}`.trim()
+}
+
+function extractNestedSignature(code: string): string {
+ const stop = code.search(/[{]/)
+ return (stop === -1 ? (code.split('\n')[0] ?? code) : code.slice(0, stop))
+ .replace(/\s+/g, ' ')
+ .trim()
+}
+
+function extractPropertySignature(code: string): string {
+ const line = code.split('\n')[0]?.trim() ?? code
+ const typed = line.match(/^((?:val|var)\s+\w+\s*:\s*[^=]+?)(?:\s*=.*)?$/)
+ if (typed != null) return (typed[1] ?? line).trim()
+ // No explicit type (e.g. `val x = internal.x`) → just `val name`.
+ const m = line.match(/^((?:val|var)\s+\w+)/)
+ return (m?.[1] ?? line).trim()
+}
+
+// ---------------------------------------------------------------------------
+// Parse a whole type
+// ---------------------------------------------------------------------------
+
+function parseType(text: string, typeName: string): ParsedType {
+ const slice = sliceType(text, typeName)
+ let groups: MemberGroup[] = []
+ if (slice.kind === 'data class' && slice.ctorParamsRaw != null) {
+ const props = parseConstructorProps(slice.ctorParamsRaw)
+ if (props.length > 0) groups = [{ title: 'Properties', members: props }]
+ } else if (slice.bodyRaw != null) {
+ groups = extractBodyMembers(slice.bodyRaw, slice.kind)
+ }
+ return {
+ name: typeName,
+ kind: slice.kind,
+ declaration: slice.declaration,
+ doc: slice.doc,
+ groups,
+ }
+}
+
+// ---------------------------------------------------------------------------
+// MDX rendering
+// ---------------------------------------------------------------------------
+
+const AUTOGEN_NOTE =
+ '> Auto-generated from the Seam Android SDK Kotlin sources. Do not edit by hand — see `mintlify-codegen/android-reference/`.'
+
+function firstSentence(body: string): string {
+ const stripped = body
+ .replace(/```[\s\S]*?```/g, '')
+ .replace(/[`*[\]]/g, '')
+ .replace(/\s+/g, ' ')
+ .trim()
+ const m = stripped.match(/^(.*?[.!?])(\s|$)/)
+ return (m?.[1] ?? stripped).trim()
+}
+
+function yamlEscape(s: string): string {
+ return s.replace(/'/g, "''")
+}
+
+function renderParamsTable(
+ params: Array<{ name: string; desc: string }>,
+): string[] {
+ if (params.length === 0) return []
+ const rows = params.map((p) => `| \`${p.name}\` | ${p.desc || '—'} |`)
+ return [
+ '',
+ '**Parameters**',
+ '',
+ '| Parameter | Description |',
+ '| --- | --- |',
+ ...rows,
+ ]
+}
+
+function renderMember(m: Member): string[] {
+ const lines: string[] = [`### \`${m.name}\``, '']
+ if (m.doc.body) lines.push(m.doc.body, '')
+ if (m.signature) lines.push('```kotlin', m.signature, '```')
+ lines.push(...renderParamsTable(m.doc.params))
+ if (m.doc.returns) lines.push('', `**Returns** — ${m.doc.returns}`)
+ if (m.doc.throwsList.length > 0) {
+ lines.push('', '**Throws**', '')
+ for (const t of m.doc.throwsList) {
+ lines.push(`- \`${t.type}\`${t.desc ? ` — ${t.desc}` : ''}`)
+ }
+ }
+ lines.push('')
+ return lines
+}
+
+function renderTypePage(t: ParsedType): string {
+ const lines: string[] = [
+ '---',
+ `title: '${yamlEscape(t.name)}'`,
+ `description: '${yamlEscape(firstSentence(t.doc.body))}'`,
+ '---',
+ '',
+ AUTOGEN_NOTE,
+ '',
+ '## Overview',
+ '',
+ '```kotlin',
+ t.declaration,
+ '```',
+ ]
+ if (t.doc.body) lines.push('', t.doc.body)
+
+ for (const group of t.groups) {
+ lines.push('', '---', '', `## ${group.title}`, '')
+ if (group.title === 'Properties' && t.kind === 'data class') {
+ lines.push('| Property | Type | Description |', '| --- | --- | --- |')
+ for (const m of group.members) {
+ const type = m.signature.replace(/^(?:val|var)\s+\w+:\s*/, '')
+ lines.push(
+ `| \`${m.name}\` | \`${type}\` | ${m.doc.body.replace(/\n+/g, ' ') || '—'} |`,
+ )
+ }
+ lines.push('')
+ } else {
+ for (const m of group.members) lines.push(...renderMember(m))
+ }
+ }
+ return lines
+ .join('\n')
+ .replace(/\n{3,}/g, '\n\n')
+ .replace(/\n*$/, '\n')
+}
+
+function renderIndexPage(types: ParsedType[]): string {
+ const cards = types
+ .map((t) => {
+ const slug = PAGES.find((p) => p.type === t.name)?.slug ?? ''
+ const desc = firstSentence(t.doc.body) || `${t.kind} reference`
+ return [
+ ` `,
+ ` ${desc}`,
+ ' ',
+ ].join('\n')
+ })
+ .join('\n')
+ return [
+ '---',
+ `title: 'Android SDK API Reference'`,
+ `description: 'Public API surface of the Seam Android SDK.'`,
+ '---',
+ '',
+ AUTOGEN_NOTE,
+ '',
+ '',
+ cards,
+ '',
+ '',
+ ].join('\n')
+}
+
+// ---------------------------------------------------------------------------
+// Main
+// ---------------------------------------------------------------------------
+
+function resolveSourceRoot(): string {
+ const flagIndex = argv.indexOf('--source')
+ if (flagIndex !== -1 && argv[flagIndex + 1] != null) {
+ return argv[flagIndex + 1] as string
+ }
+ const fromEnv = env['ANDROID_SDK_SOURCE']
+ if (fromEnv != null) return fromEnv
+ console.error(
+ 'ERROR: provide the SDK source root via --source or ANDROID_SDK_SOURCE.',
)
+ exit(1)
+}
+
+async function main(): Promise {
+ const sourceRoot = resolveSourceRoot()
+ const __dirname = dirname(fileURLToPath(import.meta.url))
+ const outDir = join(__dirname, '..', '..', 'mintlify-docs', OUTPUT_DIR_REL)
+ await mkdir(outDir, { recursive: true })
- // Intentionally write nothing and succeed, so the sync workflow no-ops safely.
- exit(0)
+ const parsed: ParsedType[] = []
+ for (const page of PAGES) {
+ const text = await readFile(join(sourceRoot, page.sourceFile), 'utf-8')
+ const type = parseType(text, page.type)
+ parsed.push(type)
+ await writeFile(join(outDir, `${page.slug}.mdx`), renderTypePage(type))
+ console.log(
+ `✓ ${page.slug}.mdx (${type.kind}, ${type.groups.reduce((n, g) => n + g.members.length, 0)} members)`,
+ )
+ }
+ await writeFile(join(outDir, 'index.mdx'), renderIndexPage(parsed))
+ console.log(`✓ index.mdx (${parsed.length} types)`)
}
-main()
+await main()
diff --git a/mintlify-docs/mobile-sdks/android/reference/index.mdx b/mintlify-docs/mobile-sdks/android/reference/index.mdx
index efe4259a2..653011f5c 100644
--- a/mintlify-docs/mobile-sdks/android/reference/index.mdx
+++ b/mintlify-docs/mobile-sdks/android/reference/index.mdx
@@ -1,65 +1,30 @@
---
title: 'Android SDK API Reference'
-description: 'Complete API reference for the Seam Android SDK — classes, data classes, enumerations, and sealed classes.'
-sidebarTitle: 'Overview'
+description: 'Public API surface of the Seam Android SDK.'
---
-> **Interim hand-authored reference.** This page is authored directly from the Seam Android SDK public Kotlin sources. The long-term plan is to generate these pages from KDoc via Dokka.
-
-The Seam Android SDK exposes a reactive, coroutine-friendly API built around `StateFlow` properties and suspend functions. Use `SeamSDK` as the single entry point for all SDK operations.
-
-## Quick Start
-
-```kotlin
-// 1. Initialize at app launch (suspend — call from a coroutine)
-SeamSDK.initialize(context, "seam_cst_...")
-
-// 2. Activate to begin credential sync
-SeamSDK.getInstance().activate()
-
-// 3. Observe credentials
-SeamSDK.getInstance().credentials.collect { credentials ->
- // Update your UI
-}
-```
-
-## Classes
+> Auto-generated from the Seam Android SDK Kotlin sources. Do not edit by hand — see `mintlify-codegen/android-reference/`.
-
- The main entry point for the Seam Android SDK. Manages SDK lifecycle, credential synchronization, unlock operations, and StateFlow-based status updates.
+
+ This is the main entry point for the Seam Android SDK.
-
-
-## Data Classes
-
-
-
- An immutable snapshot of a single access credential — its name, location, expiry, supported unlock proximities, and any active errors.
+
+ A Seam credential object.
-
-
-## Enumerations
-
-
-
- The required physical proximity for an unlock attempt: `TOUCH`, `NEARBY`, or `REMOTE`.
+
+ Base class for all Seam errors.
-
-
-## Sealed Classes
-
-
-
- Events emitted to the `unlockStatus` StateFlow as an unlock operation progresses from scanning through access granted, timeout, or reader error.
+
+ A credential error is an error that is related to a credential.
-
- Errors thrown by `SeamSDK` methods for initialization, activation, credential, and network failures.
+
+ A user interaction that is required to unlock a credential.
-
- Credential-specific errors that appear in `SeamCredential.errors` or are thrown wrapped in `SeamError.CredentialErrors`.
+
+ Events related to the co.seam.core.api.SeamSDK.unlock operation.
-
- Actions the user must take to resolve a `SeamCredentialError.UserInteractionRequired` error — OTP authorization, enabling internet or Bluetooth, or granting permissions.
+
+ Represents the proximity at which a credential used to unlock a door.
diff --git a/mintlify-docs/mobile-sdks/android/reference/seam-credential-error.mdx b/mintlify-docs/mobile-sdks/android/reference/seam-credential-error.mdx
index e7f1295cb..d9db498a9 100644
--- a/mintlify-docs/mobile-sdks/android/reference/seam-credential-error.mdx
+++ b/mintlify-docs/mobile-sdks/android/reference/seam-credential-error.mdx
@@ -1,79 +1,62 @@
---
title: 'SeamCredentialError'
-description: 'Credential-specific errors that appear in SeamCredential.errors or are thrown wrapped in SeamError.CredentialErrors.'
+description: 'A credential error is an error that is related to a credential.'
---
-> **Interim hand-authored reference.** This page is authored directly from the Seam Android SDK public Kotlin sources. See the [reference overview](/mobile-sdks/android/reference/index) for context.
+> Auto-generated from the Seam Android SDK Kotlin sources. Do not edit by hand — see `mintlify-codegen/android-reference/`.
## Overview
-`SeamCredentialError` is a `sealed class` that extends `SeamError`. It represents errors specific to a single credential. These errors appear in two places:
+```kotlin
+sealed class SeamCredentialError : SeamError()
+```
+
+A credential error is an error that is related to a credential. When a credential error
+occurs, it means that the credential is not valid or has some issues. It can be thrown when
+trying to unlock with a credential, or they can come in the `SeamCredential.errors` list.
+
+---
+
+## Cases
+
+### `Loading`
-1. As elements of `SeamCredential.errors` — the list of active errors on a credential.
-2. As elements of the `errors` list inside `SeamError.CredentialErrors`, thrown by `SeamSDK.getInstance().unlock()`.
+The credential is still loading. This error is usually thrown when trying to unlock
+with a credential that was not fully loaded.
```kotlin
-sealed class SeamCredentialError : SeamError() {
- class Loading : SeamCredentialError()
- class Expired : SeamCredentialError()
- data class UserInteractionRequired(
- val interaction: SeamRequiredUserInteraction
- ) : SeamCredentialError()
- class Unknown : SeamCredentialError()
-}
+class Loading : SeamCredentialError()
```
----
+### `Expired`
-## Subtypes
+The credential has expired. This error is usually thrown when trying to unlock
+with a credential that has expired.
-| Subtype | Description |
-|---------|-------------|
-| `Loading` | The credential has not finished loading yet. Retry the operation shortly. |
-| `Expired` | The credential has expired and can no longer be used for unlocking. |
-| `UserInteractionRequired(val interaction: SeamRequiredUserInteraction)` | The user must take a specific action before this credential can be used. The `interaction` property describes what action is required. |
-| `Unknown` | An unclassified or unexpected credential error occurred. |
+```kotlin
+class Expired : SeamCredentialError()
+```
### `UserInteractionRequired`
+The credential requires user interaction to be unlocked. This error is usually thrown
+when trying to unlock a credential that requires user interaction, such as completing
+an OTP authorization or enabling Bluetooth.
+
```kotlin
data class UserInteractionRequired(
- val interaction: SeamRequiredUserInteraction
-) : SeamCredentialError()
```
-Indicates that a user action is needed before the credential can be used. The `interaction` property is a `SeamRequiredUserInteraction` that describes the specific action required.
+**Parameters**
-**See also:** [`SeamRequiredUserInteraction`](/mobile-sdks/android/reference/seam-required-user-interaction)
+| Parameter | Description |
+| --- | --- |
+| `interaction` | the required user interaction |
----
+### `Unknown`
-## Example
+An unknown credential error occurred.
```kotlin
-lifecycleScope.launch {
- SeamSDK.getInstance().credentials.collect { credentialsList ->
- credentialsList.forEach { credential ->
- credential.errors.forEach { error ->
- when (error) {
- is SeamCredentialError.Expired -> {
- showExpiredBanner(credential)
- }
- is SeamCredentialError.Loading -> {
- // Not ready yet — the SDK will emit an update when loaded
- showLoadingIndicator(credential)
- }
- is SeamCredentialError.UserInteractionRequired -> {
- handleUserInteraction(error.interaction)
- }
- is SeamCredentialError.Unknown -> {
- showGenericError(credential)
- }
- }
- }
- }
- }
-}
+class Unknown : SeamCredentialError()
```
-
-**See also:** [`SeamError.CredentialErrors`](/mobile-sdks/android/reference/seam-error), [`SeamRequiredUserInteraction`](/mobile-sdks/android/reference/seam-required-user-interaction)
diff --git a/mintlify-docs/mobile-sdks/android/reference/seam-credential.mdx b/mintlify-docs/mobile-sdks/android/reference/seam-credential.mdx
index 2598c0608..224af99f0 100644
--- a/mintlify-docs/mobile-sdks/android/reference/seam-credential.mdx
+++ b/mintlify-docs/mobile-sdks/android/reference/seam-credential.mdx
@@ -1,14 +1,12 @@
---
title: 'SeamCredential'
-description: 'An immutable snapshot of a single access credential — its name, location, expiry, supported unlock proximities, and any active errors.'
+description: 'A Seam credential object.'
---
-> **Interim hand-authored reference.** This page is authored directly from the Seam Android SDK public Kotlin sources. See the [reference overview](/mobile-sdks/android/reference/index) for context.
+> Auto-generated from the Seam Android SDK Kotlin sources. Do not edit by hand — see `mintlify-codegen/android-reference/`.
## Overview
-`SeamCredential` is a `data class` representing a single access credential as an immutable value snapshot. Read credentials from `SeamSDK.getInstance().credentials`; never construct them manually in production — the SDK manages their lifecycle.
-
```kotlin
data class SeamCredential(
val id: String?,
@@ -19,93 +17,27 @@ data class SeamCredential(
val cardNumber: String?,
val code: String?,
val errors: List,
- val isManaged: Boolean = true,
+ val isManaged: Boolean,
val providerName: String
)
```
-**Notes**
-
-- Instances are immutable snapshots. To get the latest state, collect `SeamSDK.getInstance().credentials` or call `SeamSDK.getInstance().refresh()`.
-- Managed credentials (`isManaged = true`) are created and controlled by the Seam API. Unmanaged credentials (`isManaged = false`) are discovered directly through a provider integration running on-device.
-- `id` is nullable — always null-check before passing it to `unlock()`.
+A Seam credential object. It contains information about a credential, such as its ID, name,
+location, expiration date, and any errors that it encountered.
---
## Properties
| Property | Type | Description |
-|----------|------|-------------|
-| `id` | `String?` | Unique identifier for this credential. May be `null` for some provider types. |
-| `name` | `String` | Display name for this credential. |
-| `location` | `String?` | Human-readable location associated with the credential (for example, building or door name). |
-| `expiry` | `LocalDateTime?` | Expiration date and time, if the credential has one. |
-| `cardNumber` | `String?` | Card number associated with the credential, if any. |
-| `code` | `String?` | Access code associated with the credential, if any. |
-| `errors` | `List` | Active errors affecting this credential. Check this list before attempting to unlock. |
-| `supportedUnlockProximities` | `List` | Proximity levels this credential supports, ordered by preference. |
-| `isManaged` | `Boolean` | `true` if this credential was created and managed by the Seam API; `false` if discovered by a provider integration. Defaults to `true`. |
-| `providerName` | `String` | Identifies the access control provider that issued this credential. |
-
----
-
-### `supportedUnlockProximities` — Proximity requirements
-
-`supportedUnlockProximities` expresses the proximity levels acceptable for an unlock using this credential. The list is **ordered by preference**:
-
-- When `SeamSDK.getInstance().unlock()` is called **without** an `unlockProximity` argument, the **first** value in this list is used as the default.
-- Pass any supported value explicitly via the `unlockProximity` parameter.
-
-**See also:** [`UnlockProximity`](/mobile-sdks/android/reference/unlock-proximity), [`SeamSDK.unlock()`](/mobile-sdks/android/reference/seam-sdk)
-
----
-
-### `providerName` — Known values
-
-The `providerName` field identifies the access control provider that issued the credential. Known values:
-
-| Value | Provider |
-|-------|----------|
-| `"hid_origo_credential_service"` | HID Origo |
-| `"salto_ks"` | Salto KS |
-| `"brivo"` | Brivo |
-| `"assa_abloy_credential_service"` | ASSA ABLOY Credential Service |
-| `"visionline_system"` | ASSA ABLOY Visionline |
-| `"latch"` | Latch |
-| `"assa_abloy_vostio"` | ASSA ABLOY Vostio |
-| `"assa_abloy_vostio_credential_service"` | ASSA ABLOY Vostio Credential Service |
-| `"salto_space"` | Salto Space |
-
-Additional providers may be added in future SDK releases. Treat unrecognized values gracefully.
-
----
-
-## Example
-
-```kotlin
-lifecycleScope.launch {
- SeamSDK.getInstance().credentials.collect { credentials ->
- credentials.forEach { credential ->
- // Check for errors before attempting to unlock
- if (credential.errors.isEmpty()) {
- val credentialId = credential.id ?: return@forEach
- SeamSDK.getInstance().unlock(credentialId = credentialId)
- } else {
- // Handle credential errors
- credential.errors.forEach { error ->
- when (error) {
- is SeamCredentialError.Expired -> showExpiredBanner(credential)
- is SeamCredentialError.Loading -> showLoadingIndicator(credential)
- is SeamCredentialError.UserInteractionRequired -> {
- handleUserInteraction(error.interaction)
- }
- is SeamCredentialError.Unknown -> showGenericError(credential)
- }
- }
- }
- }
- }
-}
-```
-
-**See also:** [`SeamCredentialError`](/mobile-sdks/android/reference/seam-credential-error)
+| --- | --- | --- |
+| `id` | `String?` | The ID of the credential. |
+| `supportedUnlockProximities` | `List` | The list of unlock proximities supported by the credential. Possible values in the list: UnlockProximity.TOUCH UnlockProximity.NEARBY UnlockProximity.REMOTE Use one of these values to pass to `SeamSDK.unlock` |
+| `name` | `String` | A human-readable name for the credential. |
+| `location` | `String?` | A human-readable location for the credential. |
+| `expiry` | `LocalDateTime?` | The date and time when the credential expires. |
+| `cardNumber` | `String?` | The card number associated with the credential. |
+| `code` | `String?` | The code associated with the credential. |
+| `errors` | `List` | A list of errors that the credential encountered. |
+| `isManaged` | `Boolean` | Indicates whether this credential was created and managed by the Seam API (`true`), or was discovered directly through the provider integration (`false`). |
+| `providerName` | `String` | The provider name as String. Possible values: "hid_origo_credential_service" "salto_ks" "brivo" "assa_abloy_credential_service" "visionline_system" "latch" "assa_abloy_vostio" "assa_abloy_vostio_credential_service" "salto_space" |
diff --git a/mintlify-docs/mobile-sdks/android/reference/seam-error.mdx b/mintlify-docs/mobile-sdks/android/reference/seam-error.mdx
index 413c40fe2..979e8c30c 100644
--- a/mintlify-docs/mobile-sdks/android/reference/seam-error.mdx
+++ b/mintlify-docs/mobile-sdks/android/reference/seam-error.mdx
@@ -1,115 +1,133 @@
---
title: 'SeamError'
-description: 'Errors thrown by SeamSDK methods for initialization, activation, credential, and network failures.'
+description: 'Base class for all Seam errors.'
---
-> **Interim hand-authored reference.** This page is authored directly from the Seam Android SDK public Kotlin sources. See the [reference overview](/mobile-sdks/android/reference/index) for context.
+> Auto-generated from the Seam Android SDK Kotlin sources. Do not edit by hand — see `mintlify-codegen/android-reference/`.
## Overview
-`SeamError` is a `sealed class` that extends `Exception`. It is the base class for all errors thrown by `SeamSDK`. Catch `SeamError` in a `try/catch` block when calling any SDK method.
-
```kotlin
sealed class SeamError : Exception()
```
+Base class for all Seam errors.
+
+These errors are thrown by Seam classes and interfaces. They are all subclasses of
+`SeamError`.
+
---
-## Subtypes
-
-| Subtype | Description |
-|---------|-------------|
-| `InitializationRequired` | `SeamSDK.initialize()` has not been called. Call it before using any other SDK method. |
-| `ActivationRequired` | The SDK has not been activated. Call `SeamSDK.getInstance().activate()` first. |
-| `IntegrationNotFound` | No provider integration was found for the specified credential. Typically thrown when the integration package (Assa Abloy, Latch, Salto, etc.) is not included in the app. |
-| `AlreadyInitialized` | `SeamSDK.initialize()` was called while the SDK is already initialized. Call `deactivate()` first to reinitialize. |
-| `DeactivationInProgress` | A deactivation is already in progress. Wait for it to complete before calling other methods. |
-| `InvalidCredentialId` | The credential ID passed to `unlock()` is not recognized by the SDK. |
-| `CredentialErrors(val errors: List)` | The credential has one or more unresolved errors. Inspect the `errors` list before attempting to unlock. |
-| `InternetConnectionRequired` | An internet connection is required for this operation. |
-| `InvalidClientSessionToken(override val message: String)` | The client session token passed to `initialize()` is malformed or invalid. |
-| `InvalidUnlockProximity` | The `unlockProximity` passed to `unlock()` is not supported by the credential. |
-| `Unknown` | An unexpected or unclassified error occurred. |
+## Cases
+
+### `InitializationRequired`
+
+The SDK has not been initialized. Call `SeamSDK.initialize` first. Usually
+thrown when trying to call a method that requires the SDK to be initialized,
+such as `SeamSDK.unlock` or `SeamSDK.activate`.
+
+```kotlin
+class InitializationRequired : SeamError()
+```
+
+### `ActivationRequired`
+
+The app user's phone has not been activated. Call `SeamSDK.activate` first.
+
+```kotlin
+class ActivationRequired : SeamError()
+```
+
+### `IntegrationNotFound`
+
+No integration found for the specified credential. Usually thrown when trying to
+unlock a credential that doesn't have an integration (Assa Abloy, Latch, Salto, etc)
+associated with it.
+
+```kotlin
+class IntegrationNotFound : SeamError()
+```
+
+### `AlreadyInitialized`
+
+The SDK is already initialized. Thrown when trying to call
+`SeamSDK.initialize` multiple times. You can call `SeamSDK.deactivate` first.
+
+```kotlin
+class AlreadyInitialized : SeamError()
+```
+
+### `DeactivationInProgress`
+
+A deactivation is already in progress. Throw when trying to call
+`SeamSDK.deactivate` multiple times.
+
+```kotlin
+class DeactivationInProgress : SeamError()
+```
+
+### `InvalidCredentialId`
+
+The credential id is invalid. This error is usually thrown when trying to unlock
+with a credential that is invalid, or not recognized by the SDK.
+
+```kotlin
+class InvalidCredentialId : SeamError()
+```
### `CredentialErrors`
+There are multiple credential errors. Thrown when trying to unlock with a credential.
+Check the `errors` list for more details.
+
```kotlin
class CredentialErrors(val errors: List) : SeamError()
```
-Thrown by `unlock()` when the target credential has one or more active errors that prevent unlocking. The `errors` list contains `SeamCredentialError` instances in priority order.
+**Parameters**
+
+| Parameter | Description |
+| --- | --- |
+| `errors` | the list of errors |
-**See also:** [`SeamCredentialError`](/mobile-sdks/android/reference/seam-credential-error)
+### `InternetConnectionRequired`
+
+The device does not have an internet connection. Usually thrown when trying to
+call a method that requires an internet connection, such as `SeamSDK.unlock`,
+`SeamSDK.initialize`, `SeamSDK.refresh`, `SeamSDK.activate` etc.
+
+```kotlin
+class InternetConnectionRequired : SeamError()
+```
### `InvalidClientSessionToken`
+The client session token is invalid. Trhown when trying to call
+`SeamSDK.initialize` with an invalid client session token.
+
```kotlin
class InvalidClientSessionToken(
- override val message: String = "Invalid client session token"
-) : SeamError()
```
-Thrown by `initialize()` when the provided token is malformed. The `message` property contains a human-readable description of the problem.
+**Parameters**
----
+| Parameter | Description |
+| --- | --- |
+| `message` | the error message |
-## Example — Handling `unlock()` errors
+### `InvalidUnlockProximity`
+
+The unlock proximity is invalid. Thrown when trying to unlock with an invalid unlock
+proximity.
```kotlin
-try {
- SeamSDK.getInstance().unlock(
- credentialId = credential.id!!,
- unlockProximity = UnlockProximity.TOUCH
- )
-} catch (seamError: SeamError) {
- when (seamError) {
- is SeamError.InitializationRequired -> {
- // SDK not initialized — call SeamSDK.initialize() first
- }
- is SeamError.ActivationRequired -> {
- // SDK not activated — call seamSDK.activate() first
- }
- is SeamError.IntegrationNotFound -> {
- // Provider SDK (e.g. Assa Abloy, Latch, Salto) not found
- // Check that the correct integration module is included
- }
- is SeamError.InvalidCredentialId -> {
- // The credential ID is not recognized
- }
- is SeamError.CredentialErrors -> {
- // Credential has unresolved errors — inspect seamError.errors
- seamError.errors.forEach { credentialError ->
- when (credentialError) {
- is SeamCredentialError.Expired -> {
- // credential is expired
- }
- is SeamCredentialError.Loading -> {
- // credential not fully loaded yet — retry shortly
- }
- is SeamCredentialError.UserInteractionRequired -> {
- handleUserInteraction(credentialError.interaction)
- }
- is SeamCredentialError.Unknown -> {
- // unknown credential error
- }
- }
- }
- }
- is SeamError.InvalidUnlockProximity -> {
- // unlockProximity not supported by this credential
- // Check credential.supportedUnlockProximities
- }
- is SeamError.InternetConnectionRequired -> {
- // No internet — offline unlock may not be possible for this credential
- }
- is SeamError.Unknown -> {
- // Unexpected error
- }
- else -> {
- // Handle any remaining cases
- }
- }
-}
+class InvalidUnlockProximity : SeamError()
```
-**See also:** [`SeamCredentialError`](/mobile-sdks/android/reference/seam-credential-error), [`SeamSDK`](/mobile-sdks/android/reference/seam-sdk)
+### `Unknown`
+
+An unknown error occurred.
+
+```kotlin
+class Unknown : SeamError()
+```
diff --git a/mintlify-docs/mobile-sdks/android/reference/seam-required-user-interaction.mdx b/mintlify-docs/mobile-sdks/android/reference/seam-required-user-interaction.mdx
index 1af3e1765..1ee73eb49 100644
--- a/mintlify-docs/mobile-sdks/android/reference/seam-required-user-interaction.mdx
+++ b/mintlify-docs/mobile-sdks/android/reference/seam-required-user-interaction.mdx
@@ -1,81 +1,68 @@
---
title: 'SeamRequiredUserInteraction'
-description: 'Actions the user must take to resolve a SeamCredentialError.UserInteractionRequired error — OTP authorization, enabling internet or Bluetooth, or granting permissions.'
+description: 'A user interaction that is required to unlock a credential.'
---
-> **Interim hand-authored reference.** This page is authored directly from the Seam Android SDK public Kotlin sources. See the [reference overview](/mobile-sdks/android/reference/index) for context.
+> Auto-generated from the Seam Android SDK Kotlin sources. Do not edit by hand — see `mintlify-codegen/android-reference/`.
## Overview
-`SeamRequiredUserInteraction` is a `sealed class` that describes the specific action a user must take to resolve a `SeamCredentialError.UserInteractionRequired` error. Inspect the subtype to determine what to prompt the user to do.
-
```kotlin
-sealed class SeamRequiredUserInteraction {
- data class CompleteOtpAuthorization(val otpUrl: URL) : SeamRequiredUserInteraction()
- class EnableInternet : SeamRequiredUserInteraction()
- class EnableBluetooth : SeamRequiredUserInteraction()
- class GrantPermissions(val permissions: List) : SeamRequiredUserInteraction()
-}
+sealed class SeamRequiredUserInteraction
```
----
+A user interaction that is required to unlock a credential. This can be one of the following:
+- `CompleteOtpAuthorization`: complete an OTP authorization to unlock the credential.
+- `EnableInternet`: enable internet to unlock the credential.
+- `EnableBluetooth`: enable Bluetooth to unlock the credential.
+- `GrantPermissions`: grant the required permissions to unlock the credential.
-## Subtypes
+---
-| Subtype | Description |
-|---------|-------------|
-| `CompleteOtpAuthorization(val otpUrl: URL)` | The user must complete OTP authorization at the provided URL to unlock with this credential. Open `otpUrl` in a browser. |
-| `EnableInternet` | The user must enable internet connectivity on their device. |
-| `EnableBluetooth` | The user must enable Bluetooth on their device. |
-| `GrantPermissions(val permissions: List)` | The user must grant one or more permissions to the app. The `permissions` list contains the Android permission strings that need to be granted. |
+## Cases
### `CompleteOtpAuthorization`
+Complete an OTP authorization to unlock with the credential. The user needs to go to
+`otpUrl` to complete the authorization.
+
```kotlin
data class CompleteOtpAuthorization(val otpUrl: URL) : SeamRequiredUserInteraction()
```
-The user must visit `otpUrl` in a browser to complete a one-time-password authorization step. This is required by certain access control providers before their credentials can be used.
+**Parameters**
-### `GrantPermissions`
+| Parameter | Description |
+| --- | --- |
+| `otpUrl` | the URL to complete the OTP authorization |
+
+### `EnableInternet`
+
+Enable internet to unlock with the credential. The user needs to enable internet on their device.
```kotlin
-class GrantPermissions(val permissions: List) : SeamRequiredUserInteraction()
+class EnableInternet : SeamRequiredUserInteraction()
```
-The `permissions` list contains Android permission strings (for example, `"android.permission.BLUETOOTH_SCAN"`) that the app must request from the user via the standard Android permissions API.
+### `EnableBluetooth`
----
+Enable Bluetooth to unlock the credential. The user needs to enable Bluetooth on their device.
+
+```kotlin
+class EnableBluetooth : SeamRequiredUserInteraction()
+```
-## Example
+### `GrantPermissions`
+
+Grant the required permissions to unlock with the credential. The user needs to grant the
+required permissions on their device.
```kotlin
-fun handleUserInteraction(interaction: SeamRequiredUserInteraction) {
- when (interaction) {
- is SeamRequiredUserInteraction.CompleteOtpAuthorization -> {
- // Open the OTP URL in a browser or WebView
- val intent = Intent(Intent.ACTION_VIEW, Uri.parse(interaction.otpUrl.toString()))
- startActivity(intent)
- }
- is SeamRequiredUserInteraction.EnableInternet -> {
- // Prompt the user to enable Wi-Fi or mobile data
- showEnableInternetDialog()
- }
- is SeamRequiredUserInteraction.EnableBluetooth -> {
- // Prompt the user to enable Bluetooth
- val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
- startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT)
- }
- is SeamRequiredUserInteraction.GrantPermissions -> {
- // Request the required permissions from the user
- ActivityCompat.requestPermissions(
- this,
- interaction.permissions.toTypedArray(),
- REQUEST_PERMISSIONS
- )
- }
- }
-}
+class GrantPermissions(val permissions: List) : SeamRequiredUserInteraction()
```
-**See also:** [`SeamCredentialError.UserInteractionRequired`](/mobile-sdks/android/reference/seam-credential-error)
+**Parameters**
+
+| Parameter | Description |
+| --- | --- |
+| `permissions` | the permissions that need to be granted |
diff --git a/mintlify-docs/mobile-sdks/android/reference/seam-sdk.mdx b/mintlify-docs/mobile-sdks/android/reference/seam-sdk.mdx
index 0393e8715..4cd5b862b 100644
--- a/mintlify-docs/mobile-sdks/android/reference/seam-sdk.mdx
+++ b/mintlify-docs/mobile-sdks/android/reference/seam-sdk.mdx
@@ -1,59 +1,67 @@
---
title: 'SeamSDK'
-description: 'The main entry point for the Seam Android SDK. Manages SDK lifecycle, credential synchronization, unlock operations, and StateFlow-based status updates.'
+description: 'This is the main entry point for the Seam Android SDK.'
---
-> **Interim hand-authored reference.** This page is authored directly from the Seam Android SDK public Kotlin sources. See the [reference overview](/mobile-sdks/android/reference/index) for context.
+> Auto-generated from the Seam Android SDK Kotlin sources. Do not edit by hand — see `mintlify-codegen/android-reference/`.
## Overview
-`SeamSDK` is the single entry point for all Seam Android SDK operations. It is a `class` with a private constructor — obtain the instance via `SeamSDK.getInstance()` after calling `SeamSDK.initialize()`.
-
```kotlin
-class SeamSDK private constructor()
+class SeamSDK()
```
-**Thread Safety**
+This is the main entry point for the Seam Android SDK.
-- All `StateFlow` properties (`credentials`, `isActivated`, `unlockStatus`) are thread-safe.
-- All suspend functions can be called from any coroutine context.
-- `initialize` is thread-safe and idempotent.
+Use the `getInstance` function to get the instance of this class.
+You can only get the instance of the class after you have initialized the SDK by calling
+`initialize`.
-**Offline Behavior**
+The `initialize` function will initialize the SDK.
-- The SDK caches credentials and can work offline for unlock operations.
-- `activate()` requires an internet connection on first activation.
-- `refresh()` always requires an internet connection.
-- `unlock()` can work offline if credentials are already cached.
+## Thread Safety
+- All StateFlow properties (`credentials`, `isActivated`, `unlockStatus`) are thread-safe
+- All suspend functions can be called from any coroutine context
+- `initialize` is thread-safe and idempotent
----
+## StateFlow Lifecycle
+- `credentials`: Emits whenever credential list changes, survives configuration changes
+- `unlockStatus`: Emits unlock events, latest event is always available via .value
+- `isActivated`: Reflects current SDK activation state
-## Companion Object Methods
+Collecting these flows is lifecycle-safe when used with Android lifecycle-aware scopes
-### `initialize(context, clientSessionToken)`
+## Offline Behavior
+- SDK caches credentials and can work offline for unlock operations
+- `activate` requires internet connection initially
+- `refresh` requires internet connection
+- `unlock` can work offline if credentials are already cached
-Initializes the Seam Android SDK. Call this once at app launch, typically in `Application.onCreate()`.
+## Error Recovery
+- Most operations can be safely retried after fixing underlying issues
+- Credential errors (in SeamCredential.errors) should be resolved before unlock
+- Network errors during activate/refresh are transient and can be retried
-```kotlin
-suspend fun initialize(context: Context, clientSessionToken: String)
-```
+## Background Processing
+- Unlock operations continue in background
+- StateFlow emissions work across app lifecycle
-**Parameters**
+## Lifecycle
+- Call `initialize` once per app session, typically in Application.onCreate()
+- `activate` when user wants to use credential features
+- No explicit cleanup needed - Android handles resource cleanup
+- `deactivate` stops background operations but preserves credentials
-| Parameter | Type | Description |
-|-----------|------|-------------|
-| `context` | `Context` | The Android application context. |
-| `clientSessionToken` | `String` | A valid client session token for the app user. |
+---
-**Throws**
+## Companion object methods
-| Error | Description |
-|-------|-------------|
-| `SeamError.AlreadyInitialized` | The SDK is already initialized. Call `deactivate()` first to reinitialize. |
-| `SeamError.InternetConnectionRequired` | No internet connection is available. |
-| `SeamError.InvalidClientSessionToken` | The token format is invalid. |
-| `SeamError.DeactivationInProgress` | A deactivation is already in progress. |
+### `initialize`
+Initializes the Seam Android SDK. This should be called once at the
+start of your application.
+
+Example:
```kotlin
try {
SeamSDK.initialize(context, "seam_cst_...")
@@ -68,7 +76,7 @@ try {
is SeamError.InternetConnectionRequired -> {
// handle error when internet connection is required
}
- is SeamError.InvalidClientSessionToken -> {
+ is SeamError.InvalidClientSessionToken -> {
// handle error when client session token is invalid
}
else -> {
@@ -78,199 +86,318 @@ try {
}
```
----
-
-### `getInstance()`
-
-Returns the singleton instance of `SeamSDK`. Thread-safe; can be called from any thread.
-
```kotlin
-fun getInstance(): SeamSDK
+suspend fun initialize(context: Context, clientSessionToken: String)
```
-**Returns:** The initialized `SeamSDK` instance.
+**Parameters**
+
+| Parameter | Description |
+| --- | --- |
+| `context` | the Android context. |
+| `clientSessionToken` | the client session token for the app user. |
**Throws**
-| Error | Description |
-|-------|-------------|
-| `SeamError.InitializationRequired` | `initialize()` has not been called yet. |
+- `SeamError.AlreadyInitialized` — if the SDK is already initialized.
+- `SeamError.InternetConnectionRequired` — if no internet connection is available.
+- `SeamError.InvalidClientSessionToken` — if the token format is invalid.
+- `SeamError.DeactivationInProgress` — if a deactivation is in progress.
+
+### `getInstance`
+
+Returns the instance of `SeamSDK`. This can be used to get a handle to the
+SeamSDK after it has been initialized.
+
+This method is thread-safe and can be called from any thread.
```kotlin
-val seamSDK = SeamSDK.getInstance()
+fun getInstance() : SeamSDK
```
+**Returns** — The initialized SeamSDK instance.
+
+**Throws**
+
+- `SeamError.InitializationRequired` — if `initialize` has not been called yet.
+
---
-## StateFlow Properties
+## Properties
-### `credentials`
+### `isActivated`
-The current list of `SeamCredential` objects for the app user. Emits an updated list whenever credentials change.
+Returns whether the app user's phone has been activated.
+
+This StateFlow emits `true` after successful `activate` call and `false` after `deactivate`.
+The flow is thread-safe and can be collected from any coroutine context.
+Latest value is always available via `.value` property.
```kotlin
-val credentials: StateFlow>
+val isActivated
```
-The list may be empty while loading or if no credentials are available. Check each credential's `errors` property to determine if it is ready for use. Latest value is always available via `.value`.
+### `credentials`
+
+Returns the list of `SeamCredential` for the current app user.
+
+This StateFlow emits an immutable list whenever credentials change.
+The list may be empty if no credentials are available or while loading.
+Check each credential's `errors` property to determine if it's ready for use.
+
+The flow is thread-safe and can be collected from any coroutine context.
+Latest value is always available via `.value` property.
```kotlin
-// Observe credential changes
-lifecycleScope.launch {
- seamSDK.credentials.collect { credentials ->
- if (credentials.isEmpty()) {
- // handle no credentials
- } else {
- // display credentials
- }
+// Observe credentials
+seamSDK.credentials.collect { credentials ->
+ // credentials is a list of [SeamCredential]
+ if (credentials.isEmpty()) {
+ // handle no credentials
+ } else {
+ // handle credentials (e.g. display them)
}
}
-// Handle credential errors
-lifecycleScope.launch {
- SeamSDK.getInstance().credentials.collect { credentialsList ->
- val errors = credentialsList.flatMap { it.errors }
- errors.forEach { error ->
- when (error) {
- is SeamCredentialError.Expired -> { /* handle expiration */ }
- is SeamCredentialError.Loading -> { /* handle not loaded yet */ }
- is SeamCredentialError.Unknown -> { /* handle unknown error */ }
- is SeamCredentialError.UserInteractionRequired -> {
- handleUserInteractionRequired(error.interaction)
- }
+// handle credential errors
+SeamSDK.getInstance().credentials.collect { credentialsList ->
+ val errors = credentialsList.flatMap { it.errors }
+ errors.forEach { error ->
+ when (error) {
+ is SeamCredentialError.Expired -> { /* handle credential expiration error */ }
+ is SeamCredentialError.Loading -> { /* handle not loaded yet */ }
+ is SeamCredentialError.Unknown -> { /* handle unknown error */ }
+ is SeamCredentialError.UserInteractionRequired -> {
+ handleUserInteractionRequired(error.interaction)
}
}
}
}
-```
-**See also:** [`SeamCredential`](/mobile-sdks/android/reference/seam-credential)
+fun handleUserInteractionRequired(interaction: SeamRequiredUserInteraction) {
+ when (interaction) {
+ is SeamRequiredUserInteraction.CompleteOtpAuthorization -> { /* handle OTP authorization */ }
+ is SeamRequiredUserInteraction.EnableBluetooth -> { /* handle Bluetooth error */ }
+ is SeamRequiredUserInteraction.EnableInternet -> { /* handle Internet connection error*/ }
+ is SeamRequiredUserInteraction.GrantPermissions -> { /* handle permissions error*/ }
+ }
+}
+```
----
+```kotlin
+val credentials
+```
-### `isActivated`
+### `unlockStatus`
-Whether the SDK is currently activated. Emits `true` after a successful `activate()` call and `false` after `deactivate()`.
+Returns the current status of the unlock feature.
-```kotlin
-val isActivated: StateFlow
-```
+This StateFlow emits `SeamUnlockEvent` instances as unlock operations progress.
+Events include scanning, connection, access granted, errors, and timeout.
+The flow retains the latest event, accessible via `.value` property.
-Latest value is always available via `.value`.
+Subscribe to this flow before calling `unlock` to ensure no events are missed.
+The flow is thread-safe and can be collected from any coroutine context.
```kotlin
-lifecycleScope.launch {
- seamSDK.isActivated.collect { activated ->
- updateActivationUI(activated)
- }
-}
+val unlockStatus
```
---
-### `unlockStatus`
+## Methods
+
+### `setUnlockEventListener`
-The current status of the unlock operation. Emits `SeamUnlockEvent` instances as unlock operations progress.
+Sets the listener for the unlock events.
```kotlin
-val unlockStatus: StateFlow
+fun setUnlockEventListener(listener: (SeamUnlockEvent) -> Unit)
```
-The flow retains the latest event, accessible via `.value`. Subscribe to this flow **before** calling `unlock()` to ensure no events are missed.
+**Parameters**
+
+| Parameter | Description |
+| --- | --- |
+| `listener` | the listener to listen for unlock events. |
+
+### `setNotification`
+
+Sets the notification for the unlock feature. Required for Assa Abloy foreground scanning.
```kotlin
-lifecycleScope.launch {
- seamSDK.unlockStatus.collect { event ->
- when (event) {
- is SeamUnlockEvent.ScanningStarted -> showScanningUI()
- is SeamUnlockEvent.AccessGranted -> showSuccessUI()
- is SeamUnlockEvent.Timeout -> showTimeoutError()
- is SeamUnlockEvent.ReaderError -> showReaderError(event.message)
- }
- }
-}
+fun setNotification(notification: Notification)
```
-**See also:** [`SeamUnlockEvent`](/mobile-sdks/android/reference/seam-unlock-event)
+**Parameters**
+
+| Parameter | Description |
+| --- | --- |
+| `notification` | the notification to use for the unlock feature. |
----
+### `setCredentialsListener`
+
+Sets the listener for the credentials.
+
+```kotlin
+fun setCredentialsListener(listener: (List) -> Unit)
+```
-## Suspend Methods
+**Parameters**
-### `activate()`
+| Parameter | Description |
+| --- | --- |
+| `listener` | the listener to listen for credentials. |
-Activates the SDK by syncing with the server and setting up background services. Requires an active internet connection on first activation. Safe to call multiple times — subsequent calls are no-ops if already activated.
+### `listCredentials`
-After successful activation, `isActivated` emits `true` and credentials begin syncing automatically.
+Lists the credentials for the current app user.
```kotlin
-suspend fun activate()
+fun listCredentials() : List
```
-**Throws**
+**Returns** — a SeamResult of a list of AcsPhoneCredential. If the SDK has not been initialized yet, the result will be a SeamFailure with `SeamError.InitializationRequired`.
+
+### `unlock`
+
+Unlocks the app user's phone.
-| Error | Description |
-|-------|-------------|
-| `SeamError.InitializationRequired` | `initialize()` has not been called. |
-| `SeamError.InternetConnectionRequired` | No internet connection is available. |
-| `SeamError.InvalidClientSessionToken` | The client session token is invalid. |
+Initiates an unlock operation for the specified credential. Progress events are emitted
+to `unlockStatus` StateFlow. The operation continues even if the app goes to background.
+
+Works offline if credentials are already cached. Requires the device to have the
+necessary hardware (Bluetooth, NFC, etc.) and permissions granted.
```kotlin
val seamSDK = SeamSDK.getInstance()
+// Start collecting unlock events before unlock
+coroutineScope.launch {
+ seamSDK.unlockStatus.collect { event ->
+ when (event) {
+ is SeamUnlockEvent.ScanningStarted -> { /* handle scanning started */ }
+ is SeamUnlockEvent.Connecting -> { /* handle connecting */ }
+ is SeamUnlockEvent.AccessGranted -> { /* handle access granted */ }
+ is SeamUnlockEvent.Timeout -> { /* handle timeout */ }
+ is SeamUnlockEvent.ReaderError -> { /* handle reader error */ }
+ else -> { /* handle other events */ }
+ }
+ }
+}
+
+// Perform unlock
try {
- seamSDK.activate()
+ val credentialId = credential.id
+ // Timeout is optional
+ seamSDK.unlock(
+ credentialId = credentialId,
+ unlockProximity = UnlockProximity.TOUCH,
+ timeout = 30.seconds
+ )
} catch (seamError: SeamError) {
when (seamError) {
+ is SeamError.ActivationRequired -> {
+ // handle error when SDK is not activated
+ }
+ is SeamError.CredentialErrors -> {
+ val credentialErrors = seamError.errors
+ handleCredentialErrors(credentialErrors)
+ // handle error when there are credential errors
+ }
is SeamError.InitializationRequired -> {
// handle error when SDK is not initialized
}
- is SeamError.InternetConnectionRequired -> {
- // handle error when internet connection is required
+ is SeamError.IntegrationNotFound -> {
+ // handle error when integration is not found, Such as Assa Abloy, Latch and Salto
}
- is SeamError.InvalidClientSessionToken -> {
- // handle error when client session token is invalid
+ is SeamError.InvalidCredentialId -> {
+ // handle error when credential ID is invalid
}
else -> {
// handle other errors
}
}
}
-```
----
+// Handle credential errors on unlock
+fun handleCredentialErrors(credentialErrors: List) {
+ credentialErrors.forEach { credentialError ->
+ when (credentialError) {
+ is SeamCredentialError.Invalid -> {
+ // handle error when credential is invalid
+ }
+
+ is SeamCredentialError.Expired -> {
+ // handle error when credential is expired
+ }
-### `deactivate(deintegrate)`
+ is SeamCredentialError.Loading -> {
+ // handle error when credential is not loaded yet
+ }
-Deactivates the SDK. Stops background operations and releases resources. Credentials remain cached and available for the next activation. Safe to call multiple times.
+ is SeamCredentialError.UserInteractionRequired -> {
+ // handle user interaction required credential error
+ }
+ is SeamCredentialError.InvalidUnlockProximity -> {
+ // handle invalid unlock proximity credential error
+ }
+ is SeamCredentialError.Unknown -> {
+ // handle unknown credential error
+ }
+ }
+ }
+}
+```
```kotlin
-suspend fun deactivate(deintegrate: Boolean = false)
+fun unlock(credentialId: CredentialId, unlockProximity: UnlockProximity? = null, timeout: Duration? = null) : Job
```
**Parameters**
-| Parameter | Type | Default | Description |
-|-----------|------|---------|-------------|
-| `deintegrate` | `Boolean` | `false` | If `true`, removes the device association and clears all state. |
+| Parameter | Description |
+| --- | --- |
+| `credentialId` | the credential ID to unlock. CredentialId is a type alias for String. |
+| `timeout` | the timeout for the unlock operation. If null, uses provider default. |
+| `unlockProximity` | the unlock proximity `UnlockProximity`. If null, uses provider default, which is the first in the list of `SeamCredential.supportedUnlockProximities`. The `SeamCredential` must have at least one supported unlock proximity. |
+
+**Returns** — a Job that can be used to cancel the unlock operation.
**Throws**
-| Error | Description |
-|-------|-------------|
-| `SeamError.InitializationRequired` | `initialize()` has not been called. |
-| `SeamError.DeactivationInProgress` | A deactivation is already in progress. |
+- `SeamError.InitializationRequired` — if the SDK has not been initialized yet.
+- `SeamError.IntegrationNotFound` — if the integration is not found.
+- `SeamError.InvalidCredentialId` — if the credential ID is invalid.
+- `SeamError.ActivationRequired` — if the app user's phone has not been activated yet.
+- `SeamError.CredentialErrors` — if the credential has unresolved errors.
+- `SeamError.InvalidUnlockProximity` — if the unlock proximity is invalid.
+
+### `suspend activate`
+
+Activates the app user's phone.
+
+Prepares the SDK for use by syncing with the server and setting up background services.
+This operation requires an active internet connection on first activation.
+Safe to call multiple times - subsequent calls are no-ops if already activated.
+
+After successful activation, `isActivated` will emit `true` and credentials will
+start syncing automatically.
```kotlin
val seamSDK = SeamSDK.getInstance()
try {
- seamSDK.deactivate(deintegrate = true)
+ // activate is a suspend function
+ seamSDK.activate()
} catch (seamError: SeamError) {
when (seamError) {
is SeamError.InitializationRequired -> {
- // handle error when SDK is not initialized
+ // handle error when SDK is already initialized
}
- is SeamError.DeactivationInProgress -> {
- // handle error when deactivation is already in progress
+ is SeamError.InternetConnectionRequired -> {
+ // handle error when internet connection is required
+ }
+ is SeamError.InvalidClientSessionToken -> {
+ // handle error when client session token is invalid
}
else -> {
// handle other errors
@@ -279,36 +406,37 @@ try {
}
```
----
+```kotlin
+suspend fun activate()
+```
-### `refresh()`
+**Throws**
-Manually syncs credentials with the server and updates the `credentials` StateFlow. Requires an active internet connection. In most cases, automatic background sync makes manual refresh unnecessary.
+- `SeamError.InitializationRequired` — if the SDK has not been initialized yet.
+- `SeamError.InternetConnectionRequired` — if no internet connection is available.
+- `SeamError.InvalidClientSessionToken` — if the client session token is invalid.
-```kotlin
-suspend fun refresh(): List
-```
+### `suspend deactivate`
-**Returns:** The updated list of `SeamCredential` objects after the refresh completes.
+Deactivates the app user's phone.
-**Throws**
+Stops background operations and cleans up resources. Credentials remain cached
+and available for the next activation. After deactivation, `isActivated` will emit `false`.
-| Error | Description |
-|-------|-------------|
-| `SeamError.InitializationRequired` | `initialize()` has not been called. |
-| `SeamError.DeactivationInProgress` | A deactivation is in progress. |
+This method is safe to call multiple times and will not throw if already deactivated.
```kotlin
val seamSDK = SeamSDK.getInstance()
try {
- val updatedCredentials = seamSDK.refresh()
+ // deactivate is a suspend function
+ seamSDK.deactivate(deintegrate = true)
} catch (seamError: SeamError) {
when (seamError) {
is SeamError.InitializationRequired -> {
// handle error when SDK is not initialized
}
is SeamError.DeactivationInProgress -> {
- // handle error when app is being deactivated
+ // handle error when internet connection is required
}
else -> {
// handle other errors
@@ -317,169 +445,58 @@ try {
}
```
----
-
-## Non-Suspend Methods
-
-### `unlock(credentialId, unlockProximity?, timeout?)`
-
-Initiates an unlock operation for the specified credential. Progress events are emitted to the `unlockStatus` StateFlow. The operation continues even if the app moves to the background. Works offline if credentials are already cached.
-
```kotlin
-fun unlock(
- credentialId: CredentialId,
- unlockProximity: UnlockProximity? = null,
- timeout: Duration? = null
-): Job
+suspend fun deactivate(deintegrate: Boolean = false)
```
-`CredentialId` is a type alias for `String`.
-
**Parameters**
-| Parameter | Type | Default | Description |
-|-----------|------|---------|-------------|
-| `credentialId` | `CredentialId` | — | The credential ID to unlock. |
-| `unlockProximity` | `UnlockProximity?` | `null` | The required proximity for this attempt. If `null`, uses the provider default (the first value in `SeamCredential.supportedUnlockProximities`). |
-| `timeout` | `Duration?` | `null` | Maximum time to wait for the unlock operation. If `null`, uses the provider default. |
-
-**Returns:** A `Job` that can be used to cancel the unlock operation.
+| Parameter | Description |
+| --- | --- |
+| `deintegrate` | If true, removes device association and clears all state. |
**Throws**
-| Error | Description |
-|-------|-------------|
-| `SeamError.InitializationRequired` | `initialize()` has not been called. |
-| `SeamError.ActivationRequired` | The SDK has not been activated. |
-| `SeamError.IntegrationNotFound` | No integration found for the credential's provider. |
-| `SeamError.InvalidCredentialId` | The credential ID is not recognized by the SDK. |
-| `SeamError.CredentialErrors` | The credential has one or more unresolved errors. |
-| `SeamError.InvalidUnlockProximity` | The specified unlock proximity is not supported by this credential. |
+- `SeamError.InitializationRequired` — if the SDK has not been initialized yet.
+- `SeamError.DeactivationInProgress` — if a deactivation is already in progress.
-```kotlin
-val seamSDK = SeamSDK.getInstance()
+### `suspend refresh`
-// Start collecting unlock events before calling unlock
-coroutineScope.launch {
- seamSDK.unlockStatus.collect { event ->
- when (event) {
- is SeamUnlockEvent.ScanningStarted -> { /* handle scanning started */ }
- is SeamUnlockEvent.AccessGranted -> { /* handle access granted */ }
- is SeamUnlockEvent.Timeout -> { /* handle timeout */ }
- is SeamUnlockEvent.ReaderError -> { /* handle reader error */ }
- }
- }
-}
+Refreshes the credentials for the current app user.
-// Perform unlock
+Manually triggers a sync with the server to fetch latest credentials.
+This operation requires an active internet connection. You should call
+`initialize` and `activate` first.
+
+The `credentials` StateFlow will be updated before this method returns.
+In most cases, automatic background sync makes manual refresh unnecessary.
+
+```kotlin
+val seamSDK = SeamSDK.getInstance()
try {
- seamSDK.unlock(
- credentialId = credential.id!!,
- unlockProximity = UnlockProximity.TOUCH,
- timeout = 30.seconds
- )
+ seamSDK.refresh()
} catch (seamError: SeamError) {
when (seamError) {
- is SeamError.ActivationRequired -> { /* SDK not activated */ }
- is SeamError.CredentialErrors -> {
- handleCredentialErrors(seamError.errors)
+ is SeamError.InitializationRequired -> {
+ // handle error when SDK is not initialized
+ }
+ is SeamError.DeactivationInProgress -> {
+ // handle error when app is being deactivated
+ }
+ else -> {
+ // handle other errors
}
- is SeamError.InitializationRequired -> { /* SDK not initialized */ }
- is SeamError.IntegrationNotFound -> { /* provider not found */ }
- is SeamError.InvalidCredentialId -> { /* bad credential ID */ }
- else -> { /* handle other errors */ }
- }
-}
-```
-
-**See also:** [`UnlockProximity`](/mobile-sdks/android/reference/unlock-proximity), [`SeamUnlockEvent`](/mobile-sdks/android/reference/seam-unlock-event), [`SeamError`](/mobile-sdks/android/reference/seam-error)
-
----
-
-### `listCredentials()`
-
-Returns the current credential list synchronously. If the SDK has not been initialized, returns an empty list.
-
-```kotlin
-fun listCredentials(): List
-```
-
-**Returns:** The current list of `SeamCredential` objects.
-
-```kotlin
-val credentials = seamSDK.listCredentials()
-```
-
----
-
-### `setUnlockEventListener(listener)`
-
-Registers a callback to receive unlock events as an alternative to collecting the `unlockStatus` StateFlow.
-
-```kotlin
-fun setUnlockEventListener(listener: (SeamUnlockEvent) -> Unit)
-```
-
-**Parameters**
-
-| Parameter | Type | Description |
-|-----------|------|-------------|
-| `listener` | `(SeamUnlockEvent) -> Unit` | A lambda invoked for each unlock event. |
-
-```kotlin
-seamSDK.setUnlockEventListener { event ->
- when (event) {
- is SeamUnlockEvent.ScanningStarted -> showScanningUI()
- is SeamUnlockEvent.AccessGranted -> showSuccessUI()
- is SeamUnlockEvent.Timeout -> showTimeoutError()
- is SeamUnlockEvent.ReaderError -> showReaderError(event.message)
}
}
```
----
-
-### `setCredentialsListener(listener)`
-
-Registers a callback to receive credential list updates as an alternative to collecting the `credentials` StateFlow.
-
-```kotlin
-fun setCredentialsListener(listener: (List) -> Unit)
-```
-
-**Parameters**
-
-| Parameter | Type | Description |
-|-----------|------|-------------|
-| `listener` | `(List) -> Unit` | A lambda invoked whenever the credential list changes. |
-
-```kotlin
-seamSDK.setCredentialsListener { credentials ->
- updateCredentialList(credentials)
-}
-```
-
----
-
-### `setNotification(notification)`
-
-Sets the foreground service notification used during unlock scanning. Required for Assa Abloy foreground scanning.
-
```kotlin
-fun setNotification(notification: Notification)
+suspend fun refresh() : List
```
-**Parameters**
-
-| Parameter | Type | Description |
-|-----------|------|-------------|
-| `notification` | `Notification` | The notification to display while the foreground service is running. |
+**Returns** — The updated list of credentials after refresh completes.
-```kotlin
-val notification = NotificationCompat.Builder(context, CHANNEL_ID)
- .setContentTitle("Scanning for doors…")
- .setSmallIcon(R.drawable.ic_lock)
- .build()
+**Throws**
-seamSDK.setNotification(notification)
-```
+- `SeamError.InitializationRequired` — if the SDK has not been initialized yet.
+- `SeamError.DeactivationInProgress` — if a deactivation is in progress.
diff --git a/mintlify-docs/mobile-sdks/android/reference/seam-unlock-event.mdx b/mintlify-docs/mobile-sdks/android/reference/seam-unlock-event.mdx
index a90e62372..b4a51d868 100644
--- a/mintlify-docs/mobile-sdks/android/reference/seam-unlock-event.mdx
+++ b/mintlify-docs/mobile-sdks/android/reference/seam-unlock-event.mdx
@@ -1,85 +1,50 @@
---
title: 'SeamUnlockEvent'
-description: 'Events emitted to the unlockStatus StateFlow as an unlock operation progresses from scanning through access granted, timeout, or reader error.'
+description: 'Events related to the co.seam.core.api.SeamSDK.unlock operation.'
---
-> **Interim hand-authored reference.** This page is authored directly from the Seam Android SDK public Kotlin sources. See the [reference overview](/mobile-sdks/android/reference/index) for context.
+> Auto-generated from the Seam Android SDK Kotlin sources. Do not edit by hand — see `mintlify-codegen/android-reference/`.
## Overview
-`SeamUnlockEvent` is a `sealed class` representing the stages of a door unlock operation. As `SeamSDK.getInstance().unlock()` progresses, the SDK emits these events to the `unlockStatus` StateFlow.
-
```kotlin
-sealed class SeamUnlockEvent {
- class ScanningStarted : SeamUnlockEvent()
- class AccessGranted : SeamUnlockEvent()
- class Timeout : SeamUnlockEvent()
- class ReaderError(val message: String) : SeamUnlockEvent()
-}
+sealed class SeamUnlockEvent
```
-Subscribe to `unlockStatus` **before** calling `unlock()` to ensure no events are missed.
+Events related to the `co.seam.core.api.SeamSDK.unlock` operation.
---
-## Subtypes
+## Cases
-| Subtype | Description |
-|---------|-------------|
-| `ScanningStarted` | The unlock operation has started scanning for a reader. |
-| `AccessGranted` | The reader granted access; entry is allowed. |
-| `Timeout` | The unlock operation timed out before completing. |
-| `ReaderError(val message: String)` | An error occurred while communicating with the reader. The `message` property contains diagnostic details. |
+### `ScanningStarted`
-### `ReaderError`
+The unlock operation has started scanning.
```kotlin
-class ReaderError(val message: String) : SeamUnlockEvent()
+class ScanningStarted : SeamUnlockEvent()
```
-Emitted when the SDK encounters an error communicating with the reader hardware. The `message` property contains diagnostic details intended for developers — do not display it directly to end users.
+### `AccessGranted`
----
+The unlock operation has been granted access.
+
+```kotlin
+class AccessGranted : SeamUnlockEvent()
+```
-## Example
+### `Timeout`
+
+The unlock operation has timed out.
```kotlin
-val seamSDK = SeamSDK.getInstance()
-
-// Subscribe before calling unlock
-lifecycleScope.launch {
- seamSDK.unlockStatus.collect { event ->
- when (event) {
- is SeamUnlockEvent.ScanningStarted -> {
- showScanningSpinner()
- }
- is SeamUnlockEvent.AccessGranted -> {
- hideScanningSpinner()
- showSuccessBanner()
- }
- is SeamUnlockEvent.Timeout -> {
- hideScanningSpinner()
- showTimeoutError()
- }
- is SeamUnlockEvent.ReaderError -> {
- hideScanningSpinner()
- // event.message contains diagnostic details
- showReaderError()
- }
- }
- }
-}
-
-// Then perform the unlock
-try {
- seamSDK.unlock(
- credentialId = credential.id!!,
- unlockProximity = UnlockProximity.TOUCH
- )
-} catch (seamError: SeamError) {
- // Handle synchronous errors thrown before scanning begins
- handleUnlockError(seamError)
-}
+class Timeout : SeamUnlockEvent()
```
-**See also:** [`SeamSDK.unlock()`](/mobile-sdks/android/reference/seam-sdk), [`UnlockProximity`](/mobile-sdks/android/reference/unlock-proximity)
+### `ReaderError`
+
+An error has occurred while communicating with the reader.
+
+```kotlin
+class ReaderError(val message: String) : SeamUnlockEvent()
+```
diff --git a/mintlify-docs/mobile-sdks/android/reference/unlock-proximity.mdx b/mintlify-docs/mobile-sdks/android/reference/unlock-proximity.mdx
index 72e1e9c1e..2c0ec7942 100644
--- a/mintlify-docs/mobile-sdks/android/reference/unlock-proximity.mdx
+++ b/mintlify-docs/mobile-sdks/android/reference/unlock-proximity.mdx
@@ -1,59 +1,30 @@
---
title: 'UnlockProximity'
-description: 'The required physical proximity for an unlock attempt: TOUCH, NEARBY, or REMOTE.'
+description: 'Represents the proximity at which a credential used to unlock a door.'
---
-> **Interim hand-authored reference.** This page is authored directly from the Seam Android SDK public Kotlin sources. See the [reference overview](/mobile-sdks/android/reference/index) for context.
+> Auto-generated from the Seam Android SDK Kotlin sources. Do not edit by hand — see `mintlify-codegen/android-reference/`.
## Overview
-`UnlockProximity` is an `enum class` representing the proximity at which a credential is used to unlock a reader. Pass one of these values to `SeamSDK.getInstance().unlock()` to specify the required proximity for an attempt.
-
```kotlin
-enum class UnlockProximity {
- TOUCH,
- NEARBY,
- REMOTE
-}
+enum class UnlockProximity
```
-If no `unlockProximity` argument is provided to `unlock()`, the SDK uses the first value in `SeamCredential.supportedUnlockProximities` as the default.
+Represents the proximity at which a credential used to unlock a door.
---
-## Cases
+## Values
-| Case | Description |
-|------|-------------|
-| `TOUCH` | The user must hold the back of the phone against the reader to unlock. |
-| `NEARBY` | The user must be physically close to the reader but does not need to touch it. |
-| `REMOTE` | The reader is unlocked from a remote location, for example via Wi-Fi. No physical proximity is required. |
+### `TOUCH`
----
+Means that user have to touch the back of the phone to unlock the reader.
-## Example
+### `NEARBY`
-```kotlin
-// Use the credential's default proximity (first in supportedUnlockProximities)
-SeamSDK.getInstance().unlock(credentialId = credential.id!!)
-
-// Require tap-to-unlock
-SeamSDK.getInstance().unlock(
- credentialId = credential.id!!,
- unlockProximity = UnlockProximity.TOUCH
-)
-
-// Require nearby Bluetooth proximity
-SeamSDK.getInstance().unlock(
- credentialId = credential.id!!,
- unlockProximity = UnlockProximity.NEARBY
-)
-
-// Unlock remotely
-SeamSDK.getInstance().unlock(
- credentialId = credential.id!!,
- unlockProximity = UnlockProximity.REMOTE
-)
-```
+Means that user have to be close to the reader to unlock it, but not necessarily touch.
+
+### `REMOTE`
-**See also:** [`SeamCredential.supportedUnlockProximities`](/mobile-sdks/android/reference/seam-credential), [`SeamSDK.unlock()`](/mobile-sdks/android/reference/seam-sdk)
+The reader is unlocked from a remote location, with wi-fi, for example.