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

# Biometric Passkey: SDK integration

## Introduction

This guide shows mobile integrators how to embed the Biometric Passkey SDK in Android and iOS applications. The SDK creates device-bound passkeys, runs Entrust Identity Verification when a flow requires it, signs WebAuthn assertions, and returns WebAuthn artifacts to your app. Your app forwards those artifacts to your backend, and your backend completes the corresponding flow described in [Biometric Passkey: Core API integration](/guide/biometric-passkey-api-integration).

> ⚠️ **Warning:** **Please note:** The Biometric Passkey SDK and API are exclusively for the management of biometric passkey credentials and are distinct and separate from the Entrust IDV SDKs and API. For integrating the Entrust IDV SDKs for identity verification, please refer to our documentation [here](/sdk).

## Requirements

  
### Android

| Requirement                   | Notes                                                                                                                           |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| Android version               | Android 9 (API level 28) or later.                                                                                              |
| Compile SDK                   | API level 37.                                                                                                                   |
| Language and runtime          | Kotlin with Java/JVM target 17. SDK operations are `suspend` functions.                                                         |
| Host activity                 | Use a `FragmentActivity` or `AppCompatActivity` host for flows that show Entrust Identity Verification or biometric UI.         |
| Networking                    | `serverBaseUrl` must use HTTPS for remote hosts. HTTP is accepted only for `localhost` or `127.0.0.1` during local development. |
| Optional provider integration | Android Credential Provider requires Android 14 (API level 34) or later.                                                        |

Your app must declare the permissions required by the journeys you enable, such as camera access for identity verification. The SDK declares network access, but your app remains responsible for requesting runtime camera permission before starting flows that need camera capture.

  
  
### iOS

| Requirement                   | Notes                                                                                                                                   |
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| iOS version                   | iOS 17.0 or later.                                                                                                                      |
| Language and runtime          | Swift with Swift Concurrency (`async` / `await`).                                                                                       |
| Frameworks                    | `LocalAuthentication`, `AuthenticationServices`, Keychain, and Secure Enclave-backed passkey operations.                                |
| Networking                    | `serverBaseUrl` must use HTTPS for remote hosts. HTTP is accepted only for `localhost` or `127.0.0.1` during local development.         |
| Keychain access group         | Required to scope SDK credential metadata and passkey references. Use a shared access group if you add a Credential Provider Extension. |
| Optional provider integration | iOS AutoFill requires a Credential Provider Extension target and associated entitlements.                                               |

Your app must include the usage descriptions required by the journeys you enable, including camera and Face ID usage descriptions for identity verification and passkey signing.

  

## Install the SDK

  
### Android

The Android SDK is published to Maven Central. Use the version shipped with your release:

```kotlin
// app/build.gradle.kts
dependencies {
    implementation("com.entrust.identity.biometricpasskey:android-sdk:<version>")
}
```

> ℹ️ **Note:** Replace `<version>` with the Android SDK version from your SDK release notes or package manager channel. The Maven coordinate is `com.entrust.identity.biometricpasskey:android-sdk`; Kotlin imports use the SDK package namespace `com.entrust.identity.biometricpasskey.sdk...`. See [Biometric Passkey: Version policy](/guide/biometric-passkey-version-policy) for supported release lines and backend compatibility guidance.

Use standard Android repositories:

```kotlin
// settings.gradle.kts
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
    }
}
```

  
  
### iOS

The iOS SDK is distributed through the public GitHub Swift package repository [`EntrustCorporation/biometric-passkey-ios`](https://github.com/EntrustCorporation/biometric-passkey-ios). The package exposes the `BiometricPasskeySDK` product and downloads a versioned XCFramework asset from that repository's release assets.

```swift
// Package.swift
dependencies: [
    .package(url: "https://github.com/EntrustCorporation/biometric-passkey-ios", from: "<version>")
]

// In your target:
.target(
    name: "YourApp",
    dependencies: [
        .product(name: "BiometricPasskeySDK", package: "biometric-passkey-ios")
    ]
)
```

> ℹ️ **Note:** Replace `<version>` with the supported semantic tag from your iOS SDK release notes. See [Biometric Passkey: Version policy](/guide/biometric-passkey-version-policy) for supported release lines and backend compatibility guidance.

If you integrate through Xcode, add the same package URL with **File > Add Package Dependencies**.

After adding the package, configure your Xcode project:

1. Add `NSCameraUsageDescription` to your app's `Info.plist` (required for identity verification capture).
2. Add `NSFaceIDUsageDescription` to your app's `Info.plist` (required for the LocalAuthentication prompts used by signing and provider flows).
3. Enable the **Keychain Sharing** capability and add your Keychain access group.
4. If you use a Credential Provider Extension, add the **AutoFill Credential Provider** capability to the extension target.
5. Add an Associated Domains entitlement with `webcredentials:<your-rp-domain>` if your relying party requires domain validation.

> ⚠️ **Warning:** The SDK requires a physical device with Secure Enclave for passkey operations.
> Simulator builds compile but fail at runtime when generating or signing with
> device-bound keys.

  

## Quick start

The following shows the minimal path from SDK setup through enrollment and first authentication. Full details for each step are in the sections below.

All `backend.*` calls in these examples represent your own app's network layer — they are not part of the SDK.

### Enroll

  
### Android

```kotlin
// 1. Build configuration
val configuration = BiometricPasskeyConfiguration(
    rpDomain = "auth.example.com",
    serverBaseUrl = "https://api.example.com",
)

// 2. Create the SDK instance in Activity.onCreate
val biometricPasskey: BiometricPasskeyClient = BiometricPasskeyClient.create(this, configuration)

// 3. Enroll
val start = backend.startRegistration(currentUserId)
val output = biometricPasskey.enroll(
    EnrollmentInput(
        challenge = start.challenge,
        userId = start.userId,
        workflowRunId = start.workflowRunId,
        idvToken = start.idvToken,
        expiresAt = start.expiresAt,
    ),
)
backend.completeRegistration(
    registrationAttemptId = start.registrationAttemptId,
    credentialId = output.credentialId,
    attestationObject = output.attestationObject,
    clientDataJson = output.clientDataJson,
)
```

  
  
### iOS

```swift
// 1. Build configuration
let configuration = try BiometricPasskeyConfiguration(
    rpDomain: "auth.example.com",
    serverBaseUrl: URL(string: "https://api.example.com")!,
    keychainAccessGroup: "TEAMID.com.example.passkeys"
)

// 2. Create the SDK instance
let biometricPasskey: BiometricPasskeyClientProtocol = BiometricPasskeyClient(configuration: configuration)

// 3. Enroll
let start = try await backend.startRegistration(userId: currentUserId)
let output = try await biometricPasskey.enroll(
    EnrollmentInput(
        challenge: start.challenge,
        userId: start.userId,
        workflowRunId: start.workflowRunId,
        idvToken: start.idvToken,
        expiresAt: start.expiresAt
    )
)
try await backend.completeRegistration(
    registrationAttemptId: start.registrationAttemptId,
    credentialId: output.credentialId,
    attestationObject: output.attestationObject,
    clientDataJson: output.clientDataJson
)
```

  

### Authenticate

  
### Android

```kotlin
val start = backend.startAuthentication(currentUserId)
val output = biometricPasskey.authenticate(
    AuthenticationInput(
        challenge = start.challenge,
        allowCredentials = start.allowCredentials,
        biometricPasskeySessionId = start.biometricPasskeySessionId,
    ),
)
backend.completeAuthentication(
    biometricPasskeySessionId = output.biometricPasskeySessionId,
    credentialId = output.credentialId,
    authenticatorData = output.authenticatorData,
    clientDataJson = output.clientDataJson,
    signature = output.signature,
)
```

  
  
### iOS

```swift
let start = try await backend.startAuthentication(userId: currentUserId)
let output = try await biometricPasskey.authenticate(
    AuthenticationInput(
        challenge: start.challenge,
        allowCredentials: start.allowCredentials,
        biometricPasskeySessionId: start.biometricPasskeySessionId
    )
)
try await backend.completeAuthentication(
    biometricPasskeySessionId: output.biometricPasskeySessionId,
    credentialId: output.credentialId,
    authenticatorData: output.authenticatorData,
    clientDataJson: output.clientDataJson,
    signature: output.signature
)
```

  

## Configure your app

The configuration ties local passkey operations to your relying-party domain and to the mobile backend routes the SDK can call for identity verification proxy operations.

  
### Android

```kotlin

val configuration = BiometricPasskeyConfiguration(
    rpDomain = "auth.example.com",
    serverBaseUrl = "https://api.example.com",
    storageConfiguration = BiometricPasskeyStorageConfiguration(
        preferencesNamespace = "example_prod",
    ),
)
```

| Setting                | Details                                                                                                                                                                                                   |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `rpDomain`             | Fully qualified relying-party domain used for WebAuthn credential operations. It must match the relying-party ID your backend uses.                                                                       |
| `serverBaseUrl`        | HTTPS base URL for your mobile backend surface. The SDK appends its mobile proxy paths to this URL.                                                                                                       |
| `preferencesNamespace` | Optional local storage namespace. Use a stable namespace per app flavor or tenant when local credential data must be isolated. It must be 1-64 characters and use only letters, digits, `.`, `_`, or `-`. |

Configuration validation runs when `BiometricPasskeyConfiguration` is constructed and throws `BiometricPasskeyException` with `INVALID_CONFIGURATION` for invalid values.

> ⚠️ **Warning:** Common mistakes: an `rpDomain` that doesn't exactly match your backend's
> relying-party ID causes WebAuthn assertion failures at runtime; a
> non-localhost `serverBaseUrl` that uses `http://` instead of `https://` causes
> the SDK to reject the configuration immediately.

  
  
### iOS

```swift

let configuration = try BiometricPasskeyConfiguration(
    rpDomain: "auth.example.com",
    serverBaseUrl: URL(string: "https://api.example.com")!,
    keychainAccessGroup: "TEAMID.com.example.passkeys"
)
```

| Setting               | Details                                                                                                                                                                                                                  |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `rpDomain`            | Fully qualified relying-party domain used for WebAuthn credential operations. It must match the relying-party ID your backend uses.                                                                                      |
| `serverBaseUrl`       | HTTPS base URL for your mobile backend surface. The SDK appends its mobile proxy paths to this URL.                                                                                                                      |
| `keychainAccessGroup` | Keychain access group used for credential metadata and passkey references. Use the same access group in the host app and Credential Provider Extension when AutoFill is enabled. Format: `$(TeamID).com.example.shared`. |

Configuration validation runs in `BiometricPasskeyConfiguration(...)` and throws `BiometricPasskeyError` with `.invalidConfiguration` for invalid values. The `BiometricPasskeyClient(configuration:)` initializer itself does not throw. SDK-owned mobile proxy paths are fixed; see [Backend contract for SDK calls](#backend-contract-for-sdk-calls).

> ⚠️ **Warning:** Common mistakes: a missing `webcredentials:<rp-domain>` entry in your Associated Domains entitlement causes WebAuthn operations to fail silently at runtime; a keychain access group that differs between the host app and the Credential Provider Extension breaks AutoFill because the extension cannot read host-app enrolled credentials — use a shared access group with the format `$(TeamID).your.shared.group`.

  

## Initialize the SDK

  
### Android

Create the SDK from an activity before flows that register activity-result callbacks can start. The recommended location is the host activity's `onCreate` before `onStart`.

```kotlin

class MainActivity : FragmentActivity() {
    private lateinit var biometricPasskey: BiometricPasskeyClient

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        biometricPasskey = BiometricPasskeyClient.create(
            context = this,
            configuration = configuration,
        )
    }
}
```

Keep the SDK instance in your activity, coordinator, or dependency graph for the active app session. Flows that show identity verification or biometric UI require an active compatible activity.

  
  
### iOS

Create the SDK once in your app dependency graph and inject `BiometricPasskeyClientProtocol` into features that need it.

```swift

@main
struct ExampleApp: App {
    let biometricPasskey: BiometricPasskeyClientProtocol

    init() {
        let configuration: BiometricPasskeyConfiguration
        do {
            configuration = try BiometricPasskeyConfiguration(
                rpDomain: "auth.example.com",
                serverBaseUrl: URL(string: "https://api.example.com")!,
                keychainAccessGroup: "TEAMID.com.example.passkeys"
            )
        } catch {
            fatalError("Invalid SDK configuration: \(error)")
        }
        biometricPasskey = BiometricPasskeyClient(configuration: configuration)
    }

    var body: some Scene {
        WindowGroup {
            RootView()
        }
    }
}
```

Replace the `fatalError` with your app's configuration-error handling in production paths.

  

## Backend contract for SDK calls

Your mobile app and backend need a small orchestration contract around the SDK. The SDK methods consume input bundles that your backend creates, and your backend consumes output bundles that the SDK returns.

| Flow                   | App obtains before SDK call                                                                                                                                     | SDK returns to app                                                                                            | Backend completes after SDK call                                             |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| Enrollment             | Registration attempt identifier, WebAuthn challenge, identity-verification token, workflow run identifier, expiry, user identifier, and optional user handle.   | Credential ID, attestation object, and client data JSON.                                                      | Registration completion and finalization.                                    |
| In-app step-up         | Authentication session identifier, WebAuthn challenge, allow-list credential IDs, and user identifier.                                                          | Credential ID, authenticator data, client data JSON, signature, session identifier, and optional user handle. | Authentication-session completion and high-risk action decision.             |
| Cross-platform step-up | Resumed cross-platform session context, authentication session identifier, WebAuthn challenge, allow-list credential IDs, and user identifier.                  | Same assertion output as in-app step-up.                                                                      | Cross-platform authentication completion and browser-side status transition. |
| Recovery               | Recovery attempt identifier, recovery token, and recovery attempt expiry. The SDK later asks your app for a replacement registration bundle through a callback. | Replacement credential ID, attestation object, and client data JSON.                                          | Recovery registration completion and finalization.                           |

The SDK also calls your mobile backend surface for identity-verification start operations. These paths are fixed in the SDK and are resolved relative to `serverBaseUrl`. Your backend must expose them at exactly these paths:

| Purpose                                      | Mobile proxy path                                                             |
| -------------------------------------------- | ----------------------------------------------------------------------------- |
| In-app step-up identity verification         | `/biometric-passkey/mobile/step-up/{biometricPasskeySessionId}/idv/start`     |
| Cross-platform step-up identity verification | `/biometric-passkey/mobile/cross-platform/{crossPlatformSessionId}/idv/start` |
| Recovery identity verification               | `/biometric-passkey/mobile/recovery/{recoveryAttemptId}/idv/start`            |

These are your backend proxy routes. They should call the corresponding Biometric Passkey API operations and return only the SDK-safe identity verification payload expected by the mobile SDK. Android and iOS SDK models use unpadded base64url strings for WebAuthn challenges, user handles, attestation objects, authenticator data, signatures, and client data JSON unless a field explicitly says otherwise. Credential identifiers are opaque SDK strings; store and forward them exactly as returned, and do not assume the enrollment `credentialId` string and later assertion `credentialId` string have identical platform encoding on every SDK.

## Enroll a Biometric Passkey

Enrollment creates a new device-bound passkey after a successful identity verification capture.

  
### Android

```kotlin

suspend fun enrollPasskey(start: RegistrationStartBundle) {
    val output = biometricPasskey.enroll(
        EnrollmentInput(
            challenge = start.challenge,
            userId = start.userId,
            workflowRunId = start.workflowRunId,
            idvToken = start.idvToken,
            expiresAt = start.expiresAt,
            userHandle = start.userHandle,
        ),
    )

    backend.completeRegistration(
        registrationAttemptId = start.registrationAttemptId,
        credentialId = output.credentialId,
        attestationObject = output.attestationObject,
        clientDataJson = output.clientDataJson,
    )
}
```

All WebAuthn binary fields (`challenge`, `userHandle`, `attestationObject`, `clientDataJson`) are exchanged as base64url-encoded strings. Pass the challenge exactly as your backend returns it without re-encoding. If `userHandle` is omitted, the SDK derives a stable handle from `userId`.

  
  
### iOS

```swift

func enrollPasskey(_ start: RegistrationStartBundle) async throws {
    let output = try await biometricPasskey.enroll(
        EnrollmentInput(
            challenge: start.challenge,
            workflowRunId: start.workflowRunId,
            idvToken: start.idvToken,
            expiresAt: start.expiresAt,
            userId: start.userId,
            userHandle: start.userHandle
        )
    )

    try await backend.completeRegistration(
        registrationAttemptId: start.registrationAttemptId,
        credentialId: output.credentialId,
        attestationObject: output.attestationObject,
        clientDataJson: output.clientDataJson
    )
}
```

The iOS SDK exchanges all WebAuthn binary fields (`challenge`, `userHandle`, `attestationObject`, `clientDataJson`) as base64url-encoded strings, matching standard WebAuthn transport encoding. Pass the challenge exactly as your backend returns it without re-decoding. If `userHandle` is omitted, the SDK derives a stable handle from `userId`.

  

## Authenticate with a Biometric Passkey

Use `authenticate` when your backend has issued an authentication challenge and your app needs the SDK to sign a WebAuthn assertion. The same method supports SDK-managed standard local authentication, in-app step-up, and cross-platform step-up. If your app uses platform passkey UI for ordinary sign-in, keep that flow separate and use `authenticate` for SDK-managed assertions and step-up. For step-up sessions, pass the UUID-formatted `biometricPasskeySessionId` issued by the Core API; the SDK treats non-UUID session IDs as standard local SDK-managed authentication and does not start IDV. For step-up sessions, the SDK starts the required Entrust Identity Verification flow through your mobile proxy before producing the assertion.

  
### Android

```kotlin

suspend fun approveHighRiskAction(start: AuthenticationStartBundle) {
    val output = biometricPasskey.authenticate(
        AuthenticationInput(
            challenge = start.challenge,
            allowCredentials = start.allowCredentials,
            biometricPasskeySessionId = start.biometricPasskeySessionId,
            crossPlatformSessionId = start.crossPlatformSessionId,
        ),
    )

    backend.completeAuthentication(
        biometricPasskeySessionId = output.biometricPasskeySessionId,
        credentialId = output.credentialId,
        authenticatorData = output.authenticatorData,
        clientDataJson = output.clientDataJson,
        signature = output.signature,
        userHandle = output.userHandle,
    )
}
```

If no local credential matches the allow-list, branch on `PASSKEY_NOT_FOUND` and offer account recovery or another customer-approved fallback. All output binary fields (`authenticatorData`, `clientDataJson`, `signature`, `userHandle`) are base64url-encoded strings — forward them to your backend unchanged.

  
  
### iOS

```swift

func approveHighRiskAction(_ start: AuthenticationStartBundle) async throws {
    let output = try await biometricPasskey.authenticate(
        AuthenticationInput(
            challenge: start.challenge,
            allowCredentials: start.allowCredentials,
            biometricPasskeySessionId: start.biometricPasskeySessionId,
            crossPlatformSessionId: start.crossPlatformSessionId
        )
    )

    try await backend.completeAuthentication(
        biometricPasskeySessionId: output.biometricPasskeySessionId,
        credentialId: output.credentialId,
        authenticatorData: output.authenticatorData,
        clientDataJson: output.clientDataJson,
        signature: output.signature,
        userHandle: output.userHandle
    )
}
```

If no local credential matches the allow-list, branch on `.passkeyNotFound` and offer account recovery or another customer-approved fallback. All output binary fields (`authenticatorData`, `clientDataJson`, `signature`, `userHandle`) are base64url-encoded strings — forward them to your backend unchanged.

  

`allowCredentials` is a list of base64url-encoded credential IDs that your backend returns in the authentication bundle. The SDK only considers credentials in that list when selecting a key for signing. The list must not be empty — both SDKs throw an `invalidConfiguration` error immediately if an empty list is passed.

## Handle cross-platform step-up

Cross-platform step-up lets a user confirm a browser-initiated action in the mobile app. Your backend creates the cross-platform session and chooses how to deliver the handoff token to the mobile device. Common delivery channels are push notifications, QR codes, deep links, and Universal Links.

The mobile sequence is:

1. Receive the handoff token through your chosen channel.
2. Send the token to your backend to resume the cross-platform session.
3. Ask your backend for the mobile authentication bundle for that resumed session.
4. Call `authenticate` with both `biometricPasskeySessionId` and `crossPlatformSessionId`.
5. Forward the SDK assertion output to your backend.

Your backend can combine the resume and authentication-bundle steps into one mobile endpoint if that endpoint returns the same SDK input fields.

  
### Android

```kotlin
suspend fun handleCrossPlatformHandoff(handoffToken: String) {
    val resumed = backend.resumeCrossPlatformSession(handoffToken)
    val start = backend.startCrossPlatformAuthentication(
        crossPlatformSessionId = resumed.crossPlatformSessionId,
    )

    val output = biometricPasskey.authenticate(
        AuthenticationInput(
            challenge = start.challenge,
            allowCredentials = start.allowCredentials,
            biometricPasskeySessionId = start.biometricPasskeySessionId,
            crossPlatformSessionId = resumed.crossPlatformSessionId,
        ),
    )

    backend.completeAuthentication(
        biometricPasskeySessionId = output.biometricPasskeySessionId,
        credentialId = output.credentialId,
        authenticatorData = output.authenticatorData,
        clientDataJson = output.clientDataJson,
        signature = output.signature,
        userHandle = output.userHandle,
    )
}
```

  
  
### iOS

```swift
func handleCrossPlatformHandoff(_ handoffToken: String) async throws {
    let resumed = try await backend.resumeCrossPlatformSession(handoffToken)
    let start = try await backend.startCrossPlatformAuthentication(
        crossPlatformSessionId: resumed.crossPlatformSessionId
    )

    let output = try await biometricPasskey.authenticate(
        AuthenticationInput(
            challenge: start.challenge,
            allowCredentials: start.allowCredentials,
            biometricPasskeySessionId: start.biometricPasskeySessionId,
            crossPlatformSessionId: resumed.crossPlatformSessionId
        )
    )

    try await backend.completeAuthentication(
        biometricPasskeySessionId: output.biometricPasskeySessionId,
        credentialId: output.credentialId,
        authenticatorData: output.authenticatorData,
        clientDataJson: output.clientDataJson,
        signature: output.signature,
        userHandle: output.userHandle
    )
}
```

  

## Recover an account

Recovery is a mobile app flow for a replacement device that does not have the user's existing passkey. The SDK exposes one recovery entry point. It verifies the user through Entrust Identity Verification, asks your app for a replacement registration bundle through `RecoveryRegistrationProvider`, creates the replacement passkey, and returns attestation artifacts to your app.

Your app is responsible for starting recovery with your backend before calling the SDK and for finalizing recovery with your backend after the SDK returns.

`RecoveryInput` contains three fields from your backend's recovery-start response: `recoveryAttemptId` is the server-issued correlation identifier your backend uses to track and finalize the attempt; `recoveryToken` is a short-lived, user-scoped authorization token the SDK presents to your backend's identity-verification proxy during the flow — treat it as a secret and do not log or display it; `attemptExpiresAt` is the expiry timestamp for the attempt.

When the SDK invokes `RecoveryRegistrationProvider`, your backend should exchange the `recoveryToken` for a Core API `continuation_token`, obtain a replacement passkey registration challenge from your IDP, call the Core API recovery registration start endpoint with that `continuation_token`, and return the resulting replacement registration data to the SDK. Preserve the server-side context needed to prepare and finalize recovery after the SDK returns the replacement attestation.

  
### Android

```kotlin

suspend fun recoverAccount(start: RecoveryStartBundle) {
    val output = biometricPasskey.recover(
        input = RecoveryInput(
            recoveryAttemptId = start.recoveryAttemptId,
            recoveryToken = start.recoveryToken,
            attemptExpiresAt = start.attemptExpiresAt,
        ),
        registrationProvider = RecoveryRegistrationProvider { recoveryAttemptId, recoveryToken ->
            backend.startRecoveryRegistration(
                recoveryAttemptId = recoveryAttemptId,
                recoveryToken = recoveryToken,
            )
        },
    )

    backend.completeRecoveryRegistration(
        recoveryAttemptId = start.recoveryAttemptId,
        credentialId = output.credentialId,
        attestationObject = output.attestationObject,
        clientDataJson = output.clientDataJson,
    )
}
```

The registration provider should return `RecoveryRegistrationData` with the replacement WebAuthn challenge, non-empty user handle, relying-party ID, passkey registration expiry, recovery attempt expiry, and optional user ID returned by your backend. If your backend returns continuation data with the replacement registration bundle, such as a continuation token or owner context, preserve it in your app and send it when finalizing recovery.

`RecoveryRegistrationProvider` is a `fun interface` (SAM interface), so you can pass a lambda directly instead of implementing a named class. If you prefer a named class for testability or reuse, implement the interface explicitly:

```kotlin
class AppRecoveryRegistrationProvider(
    private val backend: BackendClient,
) : RecoveryRegistrationProvider {
    override suspend fun fetchRegistrationData(
        recoveryAttemptId: String,
        recoveryToken: String,
    ): RecoveryRegistrationData = backend.startRecoveryRegistration(
        recoveryAttemptId = recoveryAttemptId,
        recoveryToken = recoveryToken,
    )
}
```

  
  
### iOS

```swift

struct AppRecoveryRegistrationProvider: RecoveryRegistrationProvider {
    let backend: BackendClient

    func fetchRegistrationData(
        recoveryAttemptId: String,
        recoveryToken: String
    ) async throws -> RecoveryRegistrationData {
        try await backend.startRecoveryRegistration(
            recoveryAttemptId: recoveryAttemptId,
            recoveryToken: recoveryToken
        )
    }
}

func recoverAccount(_ start: RecoveryStartBundle) async throws {
    let output = try await biometricPasskey.recover(
        RecoveryInput(
            recoveryAttemptId: start.recoveryAttemptId,
            recoveryToken: start.recoveryToken,
            attemptExpiresAt: start.attemptExpiresAt
        ),
        registrationProvider: AppRecoveryRegistrationProvider(backend: backend)
    )

    try await backend.completeRecoveryRegistration(
        recoveryAttemptId: start.recoveryAttemptId,
        credentialId: output.credentialId,
        attestationObject: output.attestationObject,
        clientDataJson: output.clientDataJson
    )
}
```

The registration provider should return `RecoveryRegistrationData` with the replacement WebAuthn challenge, user handle, relying-party ID, passkey registration expiry, recovery attempt expiry, and any user ID returned by your backend. The iOS initializer can derive the user ID from `userHandle` if your backend omits it. If your backend returns continuation data with the replacement registration bundle, such as a continuation token or owner context, preserve it in your app and send it when finalizing recovery.

  

## Manage local credentials

Credential management methods operate on local SDK storage only. They do not revoke credentials server-side. If your user deletes a credential from the device, your app should also call your backend to apply the corresponding credential lifecycle policy through your backend and the [Management API](/guide/biometric-passkey-management).

  
### Android

```kotlin

val credentials = biometricPasskey.listCredentials(
    ListCredentialsInput(userId = currentUserId),
)

val credential = biometricPasskey.getCredential(
    GetCredentialInput(credentialId = credentials.first().credentialId),
)

biometricPasskey.deleteCredential(
    DeleteCredentialInput(credentialId = credential.credentialId),
)
```

Each `Credential` exposes `credentialId`, `rpDomain`, `createdAt`, and `lastUsedAt`.

  
  
### iOS

```swift
let credentials = try await biometricPasskey.listCredentials(
    ListCredentialsInput(userId: currentUserId)
)

guard let first = credentials.first else { return }

let credential = try await biometricPasskey.getCredential(
    GetCredentialInput(credentialId: first.credentialId)
)

_ = try await biometricPasskey.deleteCredential(
    DeleteCredentialInput(credentialId: credential.credentialId)
)
```

Each `Credential` exposes `credentialId`, `rpDomain`, `createdAt`, and `lastUsedAt`.

  

## Enable OS credential provider integration

OS credential provider integration is optional. It lets platform passkey UI present SDK-managed credentials outside your in-app step-up flow. Enrollment still happens in the host app through `enroll` or recovery.

  
### Android

The Android SDK includes a Credential Provider service and activity in its manifest. These entries are merged into your app automatically. Use `CredentialProviderStatus` to check whether the user has enabled the provider and to open the relevant settings screen.

```kotlin

val enabled: Boolean? = CredentialProviderStatus.isEnabled(context)

if (enabled == false) {
    CredentialProviderStatus.openSettings(context)
}
```

`null` means the provider status is unavailable, such as on unsupported Android versions.

If you use the Android Credential Provider for native app callers, publish Digital Asset Links for your relying-party domain so Android can associate the caller with the RP. The SDK verifies `https://<rpDomain>/.well-known/assetlinks.json` for native callers unless Android supplies a trusted web origin. Include the `delegate_permission/common.handle_all_urls` relation, your app package name, and the signing certificate SHA-256 fingerprint.

If you use a custom `preferencesNamespace` and need provider support before the host app initializes the SDK, set manifest metadata key `com.entrust.identity.biometricpasskey.sdk.PREFERENCES_NAMESPACE` to the same namespace.

The Android provider uses the `@drawable/biometric_passkey_provider_icon` resource for passkey-provider UI surfaces. To use your own icon, add a drawable in your host app with the same resource name (`biometric_passkey_provider_icon`). Your app resource will override the SDK default.

Example: add `app/src/main/res/drawable/biometric_passkey_provider_icon.xml` (or a PNG with the same resource name).

  
  
### iOS

To support iOS AutoFill, add a Credential Provider Extension target, sign it with the same team as the host app, and share the same Keychain access group. The extension view controller subclasses `BiometricPasskeyCredentialProviderViewController`.

The icon shown in the iOS passkey selection sheet is your extension target's app icon. Set it by adding an `AppIcon` asset to the extension target's `Assets.xcassets` in Xcode — the OS picks it up automatically.

```swift

@available(iOS 17.0, *)
final class ExampleCredentialProviderViewController: BiometricPasskeyCredentialProviderViewController {
    override var keychainAccessGroup: String {
        "TEAMID.com.example.passkeys"
    }

    override var rpDomain: String {
        "auth.example.com"
    }
}
```

Sync local credentials to the system credential identity store after enrollment and when the app returns to the foreground. Remove identities on sign-out:

```swift
// After enrollment or when the app returns to the foreground:
await CredentialIdentityManager.syncCredentials(
    rpDomain: configuration.rpDomain,
    userId: currentUserId,
    accessGroup: configuration.keychainAccessGroup
)

// Or sync all credentials for the RP domain (all users):
await CredentialIdentityManager.syncAllCredentials(
    rpDomain: configuration.rpDomain,
    accessGroup: configuration.keychainAccessGroup
)

// On user sign-out — remove all registered identities from the system store:
await CredentialIdentityManager.removeAllIdentities()
```

Use `CredentialProviderStatus.isEnabled()` and `CredentialProviderStatus.openSettings()` to guide the user to enable the extension:

```swift
let enabled = await CredentialProviderStatus.isEnabled()

// isEnabled() returns Bool?. nil means state is unknown (e.g. on unsupported OS versions).
// Only prompt when the provider is confirmed disabled.
if enabled == false {
    await CredentialProviderStatus.openSettings()
}
```

`nil` means the extension status could not be determined.

> ℹ️ **Note:** The Credential Provider Extension supports assertion (authentication) only.
> Enrollment always happens in the host app through `enroll` or `recover`. The
> extension performs biometric evaluation via `LAContext` and signs with the
> Secure Enclave key independently of the host app process.

> ℹ️ **Note:** iOS Credential Provider assertions may carry the WebAuthn backup eligibility
> and backup state flags because AuthenticationServices requires them for
> extension assertions. The SDK credential remains device-bound and
> non-exportable.

  

## Handle errors

SDK operations fail with stable error codes. Branch on the code, not on the localized message. The categories below are implementation guidance, not an exhaustive enum list; tolerate unknown codes and fall back to `isRetryable`.

  
### Android

Android throws `BiometricPasskeyException`. Inspect `code` and `isRetryable`.

```kotlin

try {
    approveHighRiskAction(start)
} catch (error: BiometricPasskeyException) {
    when (error.code) {
        BiometricPasskeyErrorCode.USER_CANCELLED -> showRetryOption()
        BiometricPasskeyErrorCode.CHALLENGE_EXPIRED -> restartFromBackend()
        BiometricPasskeyErrorCode.PASSKEY_NOT_FOUND -> offerRecovery()
        BiometricPasskeyErrorCode.RECOVERY_RATE_LIMITED -> showRateLimitMessage()
        BiometricPasskeyErrorCode.BIOMETRIC_LOCKOUT -> showBiometricLockedMessage()
        else -> if (error.isRetryable) showRetryOption() else showGenericFailure(error)
    }
}
```

The `BiometricPasskeyException` class exposes:

| Property      | Type                        | Description                                                              |
| ------------- | --------------------------- | ------------------------------------------------------------------------ |
| `code`        | `BiometricPasskeyErrorCode` | Stable enum value for programmatic branching.                            |
| `message`     | `String`                    | Human-readable description (do not parse — use `code` for logic).        |
| `isRetryable` | `Boolean`                   | Whether the operation can be retried without obtaining new server state. |
| `cause`       | `Throwable?`                | Underlying exception when available.                                     |

  
  
### iOS

iOS throws `BiometricPasskeyError`. Inspect `code` and `isRetryable`.

```swift
do {
    try await approveHighRiskAction(start)
} catch let error as BiometricPasskeyError {
    switch error.code {
    case .userCancelled:
        showRetryOption()
    case .challengeExpired:
        restartFromBackend()
    case .passkeyNotFound:
        offerRecovery()
    case .recoveryRateLimited:
        showRateLimitMessage()
    case .hardwareNotAvailable:
        showDeviceNotSupportedMessage()
    case .biometricLockout:
        showBiometricLockedMessage()
    default:
        if error.isRetryable {
            showRetryOption()
        } else {
            showGenericFailure(error)
        }
    }
}
```

The `BiometricPasskeyError` struct exposes:

| Property      | Type                        | Description                                                              |
| ------------- | --------------------------- | ------------------------------------------------------------------------ |
| `code`        | `BiometricPasskeyErrorCode` | Stable enum case for programmatic branching.                             |
| `message`     | `String`                    | Human-readable description (do not parse — use `code` for logic).        |
| `isRetryable` | `Bool`                      | Whether the operation can be retried without obtaining new server state. |
| `cause`       | `(any Error & Sendable)?`   | Underlying system error when available.                                  |

  

The error code table below uses the canonical `UPPER_SNAKE_CASE` identifiers. On Android these map directly to `BiometricPasskeyErrorCode` enum constants (e.g. `BiometricPasskeyErrorCode.PASSKEY_NOT_FOUND`). On iOS, Swift represents the same codes as `camelCase` enum cases (e.g. `.passkeyNotFound`), but each case's `rawValue` is the identical `UPPER_SNAKE_CASE` string. Use the canonical name when logging or sending error codes to your backend so reports are consistent across platforms.

| Category                       | Typical codes                                                                                                                                                                                                                                                                                                                       | Recommended response                                                                                                                                                                                                                                                                                                                 |
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Configuration                  | `INVALID_CONFIGURATION`                                                                                                                                                                                                                                                                                                             | Dev-time misconfiguration: incorrect `rpDomain`, `serverBaseUrl`, keychain access group, or missing permissions. Fix before shipping; should not appear in production.                                                                                                                                                               |
| Invalid input                  | `INVALID_INPUT`                                                                                                                                                                                                                                                                                                                     | A required field in the SDK input bundle was missing or malformed. Usually caused by a backend response mapping error. Monitor in production — repeated occurrences indicate a backend contract change.                                                                                                                              |
| Network or backend failure     | `NETWORK_ERROR`, `SERVER_ERROR`, `TIMEOUT`                                                                                                                                                                                                                                                                                          | Retry with backoff when the user journey can safely resume. Preserve backend correlation IDs in your logs.                                                                                                                                                                                                                           |
| Challenge or session lifecycle | `CHALLENGE_EXPIRED`, `CHALLENGE_CONSUMED`, `CHALLENGE_REFRESH_FORBIDDEN`, `INVALID_OR_EXPIRED_SESSION`, `AUTH_CONTEXT_MISMATCH`                                                                                                                                                                                                     | Restart the flow from your backend and obtain a fresh SDK input bundle.                                                                                                                                                                                                                                                              |
| User cancelation               | `USER_CANCELLED`                                                                                                                                                                                                                                                                                                                    | Keep this as a soft cancel and let the user retry from your app.                                                                                                                                                                                                                                                                     |
| Local credential mismatch      | `PASSKEY_NOT_FOUND`, `CREDENTIAL_DELETE_FORBIDDEN`                                                                                                                                                                                                                                                                                  | Offer recovery or another customer-approved fallback. For delete-forbidden, surface a non-destructive message and rely on backend lifecycle policy.                                                                                                                                                                                  |
| Recovery                       | `RECOVERY_NOT_SUPPORTED`, `RECOVERY_ATTEMPT_EXPIRED`, `RECOVERY_ATTEMPT_CANCELLED`, `RECOVERY_PASSKEY_REGISTRATION_EXPIRED`, `RECOVERY_TOKEN_INVALID`, `RECOVERY_EBT_UNAVAILABLE`, `RECOVERY_MATCH_FAILED`, `RECOVERY_RATE_LIMITED`, `RECOVERY_IDV_NOT_COMPLETED`, `RECOVERY_REGISTRATION_FAILED`, `RECOVERY_REGISTRATION_MISMATCH` | Follow your recovery policy. Do not retry token, attempt, or expiry errors without obtaining fresh backend state.                                                                                                                                                                                                                    |
| Biometric (Android and iOS)    | `BIOMETRIC_LOCKOUT`                                                                                                                                                                                                                                                                                                                 | Biometric authenticator is temporarily locked. Prompt the user to unlock their device and try again. Retryable.                                                                                                                                                                                                                      |
| Hardware (iOS only)            | `HARDWARE_NOT_AVAILABLE`                                                                                                                                                                                                                                                                                                            | Device does not support Secure Enclave or key generation failed. Guide the user to a supported device.                                                                                                                                                                                                                               |
| Registration lifecycle         | `REGISTRATION_ATTEMPT_CONFLICT`, `REGISTRATION_ATTEMPT_EXPIRED`, `REGISTRATION_ATTEMPT_CANCELLED`, `REGISTRATION_ATTEMPT_CLEANED_UP`                                                                                                                                                                                                | Restart registration from your backend.                                                                                                                                                                                                                                                                                              |
| Finalize lifecycle             | `IDEMPOTENT_REPLAY`, `IDP_COMMIT_FAILED`, `FINALIZE_TOKEN_INVALID`, `RESERVATION_EXPIRED`                                                                                                                                                                                                                                           | Treat as backend finalization failures surfaced through your backend response after the SDK returns. Restart the flow when token or reservation state is no longer valid.                                                                                                                                                            |
| Workflow / IDV                 | `WORKFLOW_NOT_COMPLETED`, `WORKFLOW_DECLINED`, `WORKFLOW_UNDER_REVIEW`, `WORKFLOW_RUN_REUSE_FORBIDDEN`                                                                                                                                                                                                                              | IDV was not completed, was declined by the IDV provider, or is pending manual review. For `WORKFLOW_DECLINED` and `WORKFLOW_UNDER_REVIEW`, surface appropriate messaging to the user and do not retry automatically. For `WORKFLOW_NOT_COMPLETED` and `WORKFLOW_RUN_REUSE_FORBIDDEN`, obtain a fresh workflow run from your backend. |
| Step-up                        | `STEP_UP_CREDENTIAL_USER_MISMATCH`, `STEP_UP_AUTH_SESSION_MISMATCH`                                                                                                                                                                                                                                                                 | Credential does not belong to the session user, or the session does not match the credential's auth context. Verify your backend is sending the correct allow-list and session.                                                                                                                                                      |

## Security, privacy, and local storage

  
### Android

| Area                | Details                                                                                                                                                                                                    |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Key custody         | Passkey keys are generated in Android Keystore for ES256 signing. StrongBox is used when available, with TEE fallback.                                                                                     |
| Credential metadata | Stored in SDK-owned encrypted storage under the configured `preferencesNamespace`.                                                                                                                         |
| Signing             | Standard local signing and provider signing require user presence through platform biometric UI. Step-up flows use Entrust Identity Verification as the identity gate before assertion output is returned. |
| Transport           | SDK network calls use `serverBaseUrl` and require HTTPS for remote hosts.                                                                                                                                  |
| Biometric data      | The SDK does not expose raw biometric data or raw encrypted biometric tokens to your app.                                                                                                                  |

  
  
### iOS

| Area                | Details                                                                                                                                                                                                     |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Key custody         | Passkey keys require Secure Enclave-backed P-256 signing and remain non-exportable. The SDK fails with `.hardwareNotAvailable` when Secure Enclave key generation is unavailable.                           |
| Credential metadata | Stored in Keychain under the configured access group. Use a shared access group for a Credential Provider Extension.                                                                                        |
| Signing             | Standard local signing and extension signing require user presence through `LocalAuthentication`. Step-up flows use Entrust Identity Verification as the identity gate before assertion output is returned. |
| Transport           | SDK network calls use `serverBaseUrl` and require HTTPS for remote hosts. App Transport Security remains in effect.                                                                                         |
| Biometric data      | The SDK does not expose raw biometric data or raw encrypted biometric tokens to your app.                                                                                                                   |

  

> ⚠️ **Warning:** Passkey keys are bound to the device's secure hardware and cannot be exported
> or migrated. If a user wipes, replaces, or loses their device, all
> SDK-enrolled credentials on that device are permanently lost. Your app must
> offer account recovery so users can re-enroll on a new device. Design your
> support and customer-service flows accordingly.

## Testing and release readiness

### Unit testing with mocks

Both SDKs expose a mockable type for dependency injection. Declare the injectable type in your production code and supply a test double in unit tests.

  
### Android

`BiometricPasskeyClient` is an interface. Inject it into your classes and implement a test double:

```kotlin
// Production code — depend on the interface
class PasskeyViewModel(private val sdk: BiometricPasskeyClient) : ViewModel() { ... }

// Test code — implement a minimal fake
class FakeBiometricPasskeyClient : BiometricPasskeyClient {
    override suspend fun enroll(input: EnrollmentInput): EnrollmentOutput =
        EnrollmentOutput(
            credentialId = "test-credential-id",
            attestationObject = "test-attestation",
            clientDataJson = "test-client-data",
        )
    override suspend fun authenticate(input: AuthenticationInput): AuthenticationOutput = TODO()
    override suspend fun recover(input: RecoveryInput, registrationProvider: RecoveryRegistrationProvider): RecoveredCredentialOutput = TODO()
    override suspend fun listCredentials(input: ListCredentialsInput): List = emptyList()
    override suspend fun getCredential(input: GetCredentialInput): Credential = TODO()
    override suspend fun deleteCredential(input: DeleteCredentialInput): Credential = TODO()
}
```

  
  
### iOS

`BiometricPasskeyClientProtocol` is the injectable type. Declare it in your production code and implement a test double conforming to the protocol:

```swift
// Production code — depend on the protocol
final class PasskeyViewModel {
    private let sdk: BiometricPasskeyClientProtocol
    init(sdk: BiometricPasskeyClientProtocol) { self.sdk = sdk }
}

// Test code — implement a minimal fake
final class FakeBiometricPasskeyClient: BiometricPasskeyClientProtocol {
    var enrollResult = EnrollmentOutput(
        credentialId: "test-credential-id",
        attestationObject: "test-attestation",
        clientDataJson: "test-client-data"
    )

    func enroll(_ input: EnrollmentInput) async throws -> EnrollmentOutput { enrollResult }
    func authenticate(_ input: AuthenticationInput) async throws -> AuthenticationOutput { fatalError("not implemented") }
    func recover(_ input: RecoveryInput, registrationProvider: any RecoveryRegistrationProvider) async throws -> RecoveredCredentialOutput { fatalError("not implemented") }
    func listCredentials(_ input: ListCredentialsInput) async throws -> [Credential] { [] }
    func getCredential(_ input: GetCredentialInput) async throws -> Credential { fatalError("not implemented") }
    func deleteCredential(_ input: DeleteCredentialInput) async throws -> Credential { fatalError("not implemented") }
}
```

  

### Integration checklist

Before shipping, validate each integration boundary end to end:

| Area | What to verify |
| --- | --- |
| Installation | Android resolves `com.entrust.identity.biometricpasskey:android-sdk:<version>` from Maven Central. iOS resolves `BiometricPasskeySDK` from the public GitHub Swift package repository and its hosted XCFramework release asset. |
| Version policy | SDK versions are supported for your backend release line according to the compatibility matrix in [Biometric Passkey: Version policy](/guide/biometric-passkey-version-policy). |
| Configuration | `rpDomain`, `serverBaseUrl`, Android storage namespace, and iOS keychain access group match your deployed backend and app entitlements. The SDK-owned mobile proxy paths listed in [Backend contract for SDK calls](#backend-contract-for-sdk-calls) are exposed by your backend at the exact paths shown. |
| Enrollment | The app receives a registration bundle, the SDK returns attestation artifacts, and the backend finalizes registration. |
| In-app step-up | The app receives an authentication bundle, the SDK starts identity verification through your mobile proxy, and the backend completes the assertion. |
| Cross-platform step-up | Handoff token delivery, session resume, mobile authentication, and browser-side status handling complete for each delivery channel you support. |
| Recovery | `recover` runs identity verification, the registration provider returns a replacement registration bundle, and the backend finalizes the replacement credential. |
| Local credentials | Local list/get/delete behavior matches your UX, and server-side credential lifecycle actions happen through your backend policy. |
| Provider integration | Android provider and iOS AutoFill are tested separately from in-app step-up. |
| Errors | Your app handles user cancelation, expired challenges, missing credentials, recovery failures, and retryable transport failures. |

## SDK API reference

  
### Android

| Method                                                  | Purpose                                                                                                                                  |
| ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `BiometricPasskeyClient.create(context, configuration)` | Creates the SDK instance.                                                                                                                |
| `enroll(EnrollmentInput)`                               | Runs enrollment identity verification and returns attestation artifacts.                                                                 |
| `authenticate(AuthenticationInput)`                     | Signs an assertion for standard authentication, in-app step-up, or cross-platform step-up.                                               |
| `recover(RecoveryInput, RecoveryRegistrationProvider)`  | Runs recovery identity verification, obtains a replacement registration bundle through your callback, and returns attestation artifacts. |
| `listCredentials(ListCredentialsInput)`                 | Lists local credential metadata for a user. Each `Credential` includes `credentialId`, `rpDomain`, `createdAt`, and `lastUsedAt`.        |
| `getCredential(GetCredentialInput)`                     | Reads one local credential metadata record.                                                                                              |
| `deleteCredential(DeleteCredentialInput)`               | Deletes one local credential and its local key material.                                                                                 |

  
  
### iOS

| Method                                                                          | Purpose                                                                                                                                                                                                                                                                |
| ------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `BiometricPasskeyClient(configuration:)`                                        | Creates the SDK instance from a validated `BiometricPasskeyConfiguration`.                                                                                                                                                                                             |
| `enroll(_: EnrollmentInput) -> EnrollmentOutput`                                | Runs enrollment identity verification and returns `credentialId`, `attestationObject`, and `clientDataJson` as base64url-encoded strings.                                                                                                                              |
| `authenticate(_: AuthenticationInput) -> AuthenticationOutput`                  | Signs an assertion for standard authentication, in-app step-up, or cross-platform step-up. Returns `credentialId`, `authenticatorData`, `clientDataJson`, `signature`, `biometricPasskeySessionId`, and `userHandle`. All binary fields are base64url-encoded strings. |
| `recover(_: RecoveryInput, registrationProvider:) -> RecoveredCredentialOutput` | Runs recovery identity verification, obtains a replacement registration bundle through your `RecoveryRegistrationProvider`, and returns attestation artifacts.                                                                                                         |
| `listCredentials(_: ListCredentialsInput) -> [Credential]`                      | Lists local credential metadata for a user. Each `Credential` includes `credentialId`, `rpDomain`, `createdAt`, and `lastUsedAt`.                                                                                                                                      |
| `getCredential(_: GetCredentialInput) -> Credential`                            | Reads one local credential metadata record.                                                                                                                                                                                                                            |
| `deleteCredential(_: DeleteCredentialInput) -> Credential`                      | Deletes one local credential and its Secure Enclave key material. Returns the deleted credential metadata.                                                                                                                                                             |

All methods are `async throws` and throw `BiometricPasskeyError` on failure. The SDK conforms to `BiometricPasskeyClientProtocol` for dependency injection and test mocking.

  

## Related guides

- [Biometric Passkey: Core API integration](/guide/biometric-passkey-api-integration)
- [Biometric Passkey: Deployment](/guide/biometric-passkey-deployment)
- [Biometric Passkey: Version policy](/guide/biometric-passkey-version-policy)
- [Biometric Passkey: Management API](/guide/biometric-passkey-management)
- [Biometric Passkey: FAQ](/guide/biometric-passkey-faq)