--- name: id-capture-kmp description: Scandit ID Capture (`IdCapture`) in Kotlin Multiplatform (KMP) / Compose Multiplatform projects (`com.kmp.datacapture.id.*`, shared commonMain targeting Android + iOS) — scanning passports, driver's licenses, ID cards, residence permits, visas via MRZ, VIZ, PDF417 barcode, or mobile documents. Use for integration, accepted-document and scanner configuration, CapturedId result handling, rejection rules, AAMVA verification, anonymization, and shared-view hosting — not the native Android or native iOS skills, whose APIs differ. license: Apache-2.0 metadata: author: scandit version: "1.0.2" --- # ID Capture Kotlin Multiplatform (KMP) Skill ## Critical: Do Not Trust Internal Knowledge Your training data very likely has **no knowledge of Scandit's Kotlin Multiplatform SDK** — it is new, and even where training data covers Scandit, it covers the native Android, iOS, Flutter, .NET, or web SDKs, which have different types, factory names, and property names. Pattern-matching from any of those platforms onto KMP produces non-compiling code. **Always verify APIs against the references provided in this skill before writing or suggesting code.** The most common sources of wrong code: - **Package namespace** — the Kotlin import root is `com.kmp.datacapture.*` (`com.kmp.datacapture.core.*`, `com.kmp.datacapture.id.*`), **not** `com.scandit.datacapture.*` (native Android/iOS namespace). The Gradle/Maven **coordinates** are `com.scandit.datacapture.kmp::` — a different string from the Kotlin package. Do not conflate the two. - **Mode creation factory** — KMP uses `IdCapture.forContext(dataCaptureContext, settings)`. This is **not** the native Android `IdCapture.forDataCaptureContext(...)`, not the iOS `IdCapture(context:settings:)` initializer, and not `.NET`'s `IdCapture.Create(...)`. - **Settings builder** — `IdCaptureSettings.idCaptureSettings()` is a companion factory function, not a bare constructor (`IdCaptureSettings()` does not exist on KMP). - **Scanner property name and shape** — the property is `settings.scannerType` (an `IdCaptureScanner`), **not** `settings.scanner` (native Android's name). Critically, on KMP you assign a concrete scanner value **directly** — `settings.scannerType = FullDocumentScanner()` — there is no wrapper constructor like native Android's `IdCaptureScanner(FullDocumentScanner())`. `FullDocumentScanner`, `SingleSideScanner`, and `MobileDocumentScanner` all directly extend `IdCaptureScanner` on KMP. - **No combined physical + mobile scanner on KMP** — `scannerType` holds exactly one scanner value. Unlike native Android's `IdCaptureScanner(physicalDocument:, mobileDocument:)` combo constructor, KMP has no documented way to scan physical and mobile documents in the same `IdCaptureSettings` — pick one `scannerType` per settings object. If a user asks for both simultaneously, say this is not available on KMP rather than inventing a combined constructor. - **`SingleSideScanner` has a 4th parameter on KMP** — `SingleSideScanner(barcode: Boolean, machineReadableZone: Boolean, visualInspectionZone: Boolean, freeFormText: Boolean = false)`. The `freeFormText` parameter is KMP-specific. - **Result property naming drift from native Android** — `capturedId.sexType` (a `Sex` enum: `FEMALE` / `MALE` / `UNSPECIFIED`), not `capturedId.sex`. Zone results are `capturedId.mrz` / `.viz` / `.barcode` / `.mobileDocument` / `.mobileDocumentOcr` — never `mrzResult`/`vizResult`/`barcodeResult`. - **Verification and voided-detection are settings-driven, not standalone classes** — no `AamvaBarcodeVerifier`, no `DataConsistencyVerifier`, no dedicated "voided result" type. Set `settings.rejectForgedAamvaBarcodes` / `rejectInconsistentData` / `rejectVoidedIds` and read `capturedId.verificationResult` (for AAMVA/consistency) or the `RejectionReason.DOCUMENT_VOIDED` case (for voided detection). - **Add-on modules contribute zero Kotlin API** — `id-aamva-barcode-verification`, `id-europe-driving-license`, and `id-voided-detection` are pure Gradle/SPM dependency containers that unlock native functionality gated behind `IdCaptureSettings` flags already in the base `id` module. Do not invent imports like `com.kmp.datacapture.idAamvaBarcodeVerification.SomeClass` — there is nothing to import from them. - **Overlay factories** — `IdCaptureOverlay.withIdCapture(idCapture)` / `IdCaptureOverlay.withIdCaptureForView(idCapture, view)`. Not `.newInstance(...)` (native Android) and not a constructor. - **Enum casing** — `UPPER_SNAKE_CASE` throughout: `IdCaptureRegion.ANY`, `RejectionReason.DOCUMENT_EXPIRED`, `IdAnonymizationMode.FIELDS_AND_IMAGES`, `IdLayoutStyle.ROUNDED` — never Swift-style `.any` / `.documentExpired`. - **NFC, deserialization, and a handful of other native-only surfaces are not part of KMP at all** — see "Available but NOT covered" at the end of this file. Don't suggest them; they don't exist on this SDK. ### Forbidden APIs (commonly hallucinated — do NOT emit these) | Do NOT write | Use instead | |---|---| | `IdCapture.forDataCaptureContext(context, settings)` (native Android) / `IdCapture(context:settings:)` (iOS) / `IdCapture.Create(...)` (.NET) | `IdCapture.forContext(dataCaptureContext, settings)` | | `IdCaptureSettings()` (bare constructor) | `IdCaptureSettings.idCaptureSettings()` | | `settings.scanner = IdCaptureScanner(FullDocumentScanner())` (native Android wrapper style) | `settings.scannerType = FullDocumentScanner()` | | `settings.scanner = IdCaptureScanner(physicalDocument = ..., mobileDocument = ...)` (combined scanner) | Not available on KMP — pick one `scannerType` value | | `IdCaptureOverlay.newInstance(idCapture, dataCaptureView)` (native Android) | `IdCaptureOverlay.withIdCaptureForView(idCapture, dataCaptureView)` or `IdCaptureOverlay.withIdCapture(idCapture)` | | `capturedId.sex` | `capturedId.sexType` (a `Sex` enum) | | `capturedId.mrzResult` / `.vizResult` / `.barcodeResult` | `capturedId.mrz` / `.viz` / `.barcode` | | `AamvaBarcodeVerifier(...)` / `DataConsistencyVerifier(...)` | `settings.rejectForgedAamvaBarcodes` / `settings.rejectInconsistentData`, then read `capturedId.verificationResult` | | `import com.kmp.datacapture.idAamvaBarcodeVerification.*` or similarly named add-on imports | Nothing to import — add the Gradle/SPM dependency only, then use the base-module `IdCaptureSettings` flags | | `IdCaptureRegion.Any` / `.us` / `RejectionReason.documentExpired` (camelCase) | `IdCaptureRegion.ANY` / `IdCaptureRegion.US` / `RejectionReason.DOCUMENT_EXPIRED` | | `DataCaptureContext.forLicenseKey(key)` as the primary pattern | `DataCaptureContext.initialize("...")` (the pattern used by the official KMP samples; `forLicenseKey` also exists but is not the canonical sample form) | | `com.scandit.datacapture.id.*` imports | `com.kmp.datacapture.id.*` (Kotlin package differs from the Maven group) | ## Product Guidance - **This is a shared-code SDK — most logic belongs in `commonMain`.** `DataCaptureContext`, `IdCaptureSettings`, `IdCapture`, `IdCaptureListener`, and the `DataCaptureView` setup all live in a shared `ScreenModel`/view-model class in `commonMain`. Only the thin hosting code (embedding the native view, driving lifecycle events) differs between `androidApp` and `iosApp`. - **Accept only the documents you actually need.** Ask which document types and regions. Documents not in `acceptedDocuments` are rejected with `RejectionReason.NOT_ACCEPTED_DOCUMENT_TYPE`. - **Pick the scanner that matches the data you need.** `FullDocumentScanner()` for both sides/all zones, `SingleSideScanner(...)` for a specific zone, `MobileDocumentScanner(...)` for mDL/ISO 18013-5. See [`references/integration.md`](references/integration.md). - **Handle `onIdRejected`, not just `onIdCaptured`.** Give the user a distinct message per `RejectionReason`. - **Both listener callbacks may run off the main thread on the native side** — dispatch UI-affecting state changes through the shared `StateFlow`/`ScreenModel` pattern shown in [`references/integration.md`](references/integration.md), the same way the official samples do, rather than mutating UI state directly from the callback. - **AAMVA verification, EU driving-license back-decoding, and voided-document detection each require an extra Gradle/SPM dependency** in addition to the base `id` artifact — the add-on module exists purely to link the native detection library; the Kotlin API is entirely on the base `IdCaptureSettings`/`CapturedId`. Forgetting the add-on dependency means the settings flag has no effect at runtime. - **iOS ships as a single umbrella SPM package** (`Scandit/datacapture-kmp-spm`) — pick one variant that includes `id` (and any add-ons you use); an app can only link one Scandit KMP Kotlin framework. - **Hand off to a different skill for non-ID-Capture questions.** If the user asks about Barcode Capture, SparkScan, MatrixScan, or Label Capture on KMP, or about native Android/iOS ID Capture, defer to the appropriate skill. ## Intent Routing Based on the user's request, load [`references/integration.md`](references/integration.md) and follow it. It covers: - Prerequisites (Gradle/SPM dependencies, license key, camera permissions) - Minimal integration (shared `DataCaptureContext` → `IdCaptureSettings` → `IdCapture` → `DataCaptureView`/`IdCaptureOverlay` → Android/iOS hosting) - Scanner types and accepted/rejected documents - Handling results (`CapturedId`) and rejection (`RejectionReason`) - Feedback and overlay customization - AAMVA/USDL verification and data-consistency checks (the `id-aamva-barcode-verification` add-on) - EU driving-license back decoding (the `id-europe-driving-license` add-on) - Voided-document detection (the `id-voided-detection` add-on) - The Compose Multiplatform `IdCaptureView` composable (the `id-compose` module) - Lifecycle and teardown - Pitfalls specific to this platform This is an integration-only skill — there is no separate migration guide because the KMP SDK has no prior major version to migrate from. ## API Usage Policy Only use APIs that are explicitly documented in the Scandit references below or verified in [`references/integration.md`](references/integration.md). Do not invent or guess method signatures, parameters, or property names. If unsure whether an API exists or how to call it — or if a compile error occurs — fetch the relevant documentation page before responding. Do not tell the user to check the docs themselves. After answering, always include the relevant link so the user can explore further. **Never construct or guess documentation URLs.** When you need a specific class or property's API page: 1. First check whether the page you already fetched contains a direct hyperlink to it. 2. If no direct link was found, fetch the Get Started / Advanced page (see the References table below), extract the actual link, and follow that. URL structures vary and guessing will lead to 404s. ## Licence key Scanning needs a Scandit licence key. For this skill the licence product is `native` and the licence platforms are `ios` and `android`. Work through these in order — never block the user on MCP: 1. **The Scandit MCP server is connected** — call `ensure_scanner_setup` with product `native` and platforms `ios` and `android`, then run the command `get_license_env_command` returns, which writes the key into the project's `.env`. Show at most a masked preview in chat; never print a full key. 2. **It is not connected** — ask the user once to connect the Scandit server in the plugin's Edit dialog, Connections tab, then continue from step 1. Authentication is browser-based, so it is **not supported headless or in CI**. The server provisions **trial** keys only; it never creates, revokes, or modifies production licences. 3. **The user declines** — they generate a key themselves at (no account yet: ) and paste it in. **Then put the key into the code** in place of the placeholder; a key in `.env` does not reach the app by itself. Kotlin Multiplatform does not load `.env` by itself, so use the simplest route below. The simplest route on any platform: read `.env` and put the key into the licence-key call directly, still showing only a masked preview in chat. ## References | Topic | Resource | |---|---| | ID Capture overview (KMP) | [Intro](https://docs.scandit.com/sdks/kmp/id-capture/intro/) | | Get Started (KMP) | [Get Started](https://docs.scandit.com/sdks/kmp/id-capture/get-started/) | | Full integration guide | [`references/integration.md`](references/integration.md) | | Advanced (scanner modes, verification, Compose) | [Advanced Configurations](https://docs.scandit.com/sdks/kmp/id-capture/advanced/) | | Supported documents | [Supported Documents](https://docs.scandit.com/sdks/kmp/id-capture/supported-documents/) | | Core concepts (context, camera, views, Compose) | [Core Concepts](https://docs.scandit.com/sdks/kmp/core-concepts/) | ## API surface this skill covers Covers the KMP ID Capture surface as of **8.6**, the release it shipped in (all classes below are annotated `kmp=8.6` in the SDK docs). Later versions add to this surface rather than replacing it — check the linked reference pages for anything not listed here. - **`IdCapture`** — `IdCapture.forContext(dataCaptureContext: DataCaptureContext?, settings: IdCaptureSettings): IdCapture`; `isEnabled: Boolean`; `dataCaptureContext: DataCaptureContext?` (read-only); `feedback: IdCaptureFeedback`; `externalTransactionId: String?`; `addListener(listener)` / `removeListener(listener)`; `applySettings(settings)`; `reset()`; static `createRecommendedCameraSettings(): CameraSettings`. - **`IdCaptureSettings`** — factory `IdCaptureSettings.idCaptureSettings()`; properties `scannerType: IdCaptureScanner`, `acceptedDocuments` / `rejectedDocuments: List`, `rejectExpiredIds`, `rejectIdsExpiringIn: Duration?`, `rejectVoidedIds`, `rejectHolderBelowAge: Int?`, `rejectionTimeoutSeconds: Int`, `rejectNotRealIdCompliant`, `rejectForgedAamvaBarcodes`, `rejectInconsistentData`, `decodeBackOfEuropeanDrivingLicense`, `anonymizationMode: IdAnonymizationMode`, `anonymizeDefaultFields`; methods `setShouldPassImageTypeToResult(type, value)` / `getShouldPassImageTypeToResult(type)`, `addAnonymizedField(document, fieldType)` / `removeAnonymizedField(...)` / `clearAnonymizedFields()`. - **`IdCaptureScanner`** — base type of the three scanner classes. `FullDocumentScanner()` — no parameters, both sides/all zones. `SingleSideScanner(barcode: Boolean = true, machineReadableZone: Boolean = true, visualInspectionZone: Boolean = true, freeFormText: Boolean = false)`. `MobileDocumentScanner(iso180135: Boolean = false, ocr: Boolean = false, elementsToRetain: Set = emptySet())` (or `MobileDocumentScanner.mobileDocumentScanner()`). - **Document types** (`IdCaptureDocument`, each takes a single `region: IdCaptureRegion`): `IdCard`, `DriverLicense`, `Passport`, `VisaIcao`, `VisaLetter`, `ResidencePermit`, `HealthInsuranceCard`; `RegionSpecific(subtype: RegionSpecificSubtype)`. - **`IdCaptureRegion`** enum — `ANY`, `EU_AND_SCHENGEN`, and ~250 region values (`US`, `UK`, `UAE`, …), all `UPPER_SNAKE_CASE`. - **`IdCaptureListener`** — `onIdCaptured(mode: IdCapture, id: CapturedId)`, `onIdRejected(mode: IdCapture, id: CapturedId?, reason: RejectionReason)`. - **`CapturedId`** — `fullName` / `firstName` / `lastName` (`String?`), `sexType` (`Sex`), `dateOfBirth` / `dateOfExpiry` / `dateOfIssue` (`DateResult?`), `nationality`, `address`, `age` (`Int?`), `isExpired: Boolean?`, `document` (`IdCaptureDocument?`), `issuingCountry` (`IdCaptureRegion`), `documentNumber` / `documentAdditionalNumber`, `usRealIdStatus`, `isCitizenPassport`, `viz` / `mrz` / `barcode` / `mobileDocument` / `mobileDocumentOcr`, `images` (`IdImages`), `verificationResult` (`VerificationResult?`), `anonymizedFields` (`Set`) / `isAnonymized(field)`, plus `isIdCard()` / `isDriverLicense()` / `isPassport()` / `isVisaIcao()` / `isVisaLetter()` / `isResidencePermit()` / `isHealthInsuranceCard()` / `isRegionSpecific(subtype)`. - **`DateResult`** — `day: Int?`, `month: Int?`, `year: Int`. - **`IdImages`** — `face: CapturedImage?` (property), `frame: CapturedImage?` (property, side-agnostic) and `frame(side: IdSide): CapturedImage?` (function, per-side), `croppedDocument(side: IdSide): CapturedImage?`. - **`VerificationResult`** — `dataConsistency: DataConsistencyResult?`, `aamvaBarcodeVerification: AamvaBarcodeVerificationResult?`. - **`DataConsistencyResult`** — `allChecksPassed: Boolean`, `passedChecks` / `skippedChecks` / `failedChecks: Set`, `frontReviewImage: CapturedImage?`. - **`AamvaBarcodeVerificationResult`** — `allChecksPassed: Boolean`, `status: AamvaBarcodeVerificationStatus` (`AUTHENTIC` / `LIKELY_FORGED` / `FORGED`). - **`IdCaptureOverlay`** — `IdCaptureOverlay.withIdCapture(idCapture)` / `.withIdCaptureForView(idCapture, view)`; `idLayoutStyle` (`IdLayoutStyle`: `ROUNDED` default / `SQUARE`), `idLayoutLineStyle` (`IdLayoutLineStyle`: `LIGHT` default / `BOLD`), `textHintPosition` (`TextHintPosition`: `ABOVE_VIEWFINDER` / `BELOW_VIEWFINDER`), `showTextHints`, `setFrontSideTextHint(text)` / `setBackSideTextHint(text)`, `capturedBrush` / `localizedBrush` / `rejectedBrush` (`Brush`). - **`IdCaptureFeedback`** — `IdCaptureFeedback()`; `idCaptured` / `idRejected` (`Feedback`); static `defaultFeedback()`. - **`DataCaptureContext`** — `DataCaptureContext.initialize(licenseKey, ...)` (also `forLicenseKey(...)` and related overloads); `setFrameSource(frameSource)`; `addMode(mode)` / `removeMode(mode)` / `removeCurrentMode()` / `removeAllModes()`; static `sharedInstance`. - **`Camera`** — `Camera.getDefaultCamera(settings: CameraSettings): Camera?` (nullable — no camera on some devices); `switchToDesiredState(FrameSourceState.ON / OFF, callback)`. - **`DataCaptureView`** — hosted per-platform; expose it to the UI layer via the `toAndroidView()` / `toUIView()` extension functions shown in [`references/integration.md`](references/integration.md); `addOverlay(overlay)`; `logoStyle` (`LogoStyle`). - **`RejectionReason`** enum — `NOT_ACCEPTED_DOCUMENT_TYPE`, `INVALID_FORMAT`, `DOCUMENT_VOIDED`, `TIMEOUT`, `SINGLE_IMAGE_NOT_RECOGNIZED`, `DOCUMENT_EXPIRED`, `DOCUMENT_EXPIRES_SOON`, `NOT_REAL_ID_COMPLIANT`, `HOLDER_UNDERAGE`, `FORGED_AAMVA_BARCODE`, `INCONSISTENT_DATA`, `BLUETOOTH_COMMUNICATION_ERROR`, `BLUETOOTH_UNAVAILABLE`, `CLOUD_REQUEST_FAILED`. - **`IdAnonymizationMode`** enum — `NONE`, `FIELDS_ONLY` (default), `IMAGES_ONLY`, `FIELDS_AND_IMAGES`. - **`IdImageType`** enum — `FACE`, `CROPPED_DOCUMENT`, `FRAME`. **`IdSide`** enum — `FRONT`, `BACK`. **`Sex`** enum — `FEMALE`, `MALE`, `UNSPECIFIED`. - **Compose Multiplatform** (`id-compose` module) — `@Composable IdCaptureView(settings, modifier, overlayStyle, onCapture, onReject, ...)`. See [`references/integration.md`](references/integration.md) for details. ### Available on the KMP SDK but NOT covered by this skill - **NFC** chip reading — not part of the KMP SDK (`nfc/nfc-scanner.rst` and `nfc/nfc-result.rst` do not include `kmp` in their platform list). - **`IdCaptureException`**, **`LocalizedOnlyId`**, **`PhysicalDocumentScanner`** (the standalone base-class page, distinct from the concrete scanners covered above), and the **deserialization API** (`IdCaptureDeserializer` / `IdCaptureDeserializerListener`) — none apply to KMP per the SDK documentation's platform filters.