---
title: "Complete a device-bound passkey import (native apps)"
method: POST
path: "/user/passkey/import/complete"
tags: ["Enrollment"]
---

# Complete a device-bound passkey import (native apps)

`POST /user/passkey/import/complete`

## Request

- Base URL: `https://www.sandbox.striga.com/api/v1`
- URL: `https://www.sandbox.striga.com/api/v1/user/passkey/import/complete`
- Auth: API key in header `api-key` and API key in header `Authorization`

Submits the device key, the platform attestation and a proof of possession. Core verifies the attestation (Apple App Attest on iOS, Android Key Attestation on Android) against the application's `deviceAttestation` configuration, checks the proof, imports the key as a passkey and returns the credential plus the refreshed MFA state. Attestation is mandatory. Errors - `999995` challenge expired or mismatched, `100011` proof invalid, `100012` attestation failed (the message names the failing step), `100010` platform not configured, `100004` passkey limit reached (checked again under the subject's registration lock, so two concurrent completions cannot exceed the cap), `999998` credentialId already in use (an earlier import or a FusionAuth credential carries it).

## Request body

- object
  - `userId` string, required
  - `challengeId` string, uuid, required
  - `platform` 'IOS' | 'ANDROID', required
  - `app` object, required: `bundleId` on IOS, `packageName` on ANDROID. Must be on the application's allow-list.
    - `bundleId` string
    - `packageName` string
  - `passkey` object, required
    - `credentialId` string, required: base64url (no padding) of 16-64 bytes chosen by the app, random or derived from the key (for example SHA-256 of the SPKI); becomes the WebAuthn credential id. One credential id can be in use once across all subjects (`999998` otherwise), and it is free again after the credential is removed.
    - `publicKey` string, required: The P-256 public key, base64url - either the 65-byte X9.63 uncompressed point (iOS `SecKeyCopyExternalRepresentation`, the Android `ECPublicKey` point) or SPKI DER.
    - `algorithm` -7, required
    - `displayName` string
  - `attestation` union, required
    - object: iOS App Attest.
      - `type` 'APP_ATTEST', required
      - `keyId` string, required: From `DCAppAttestService.generateKey()`, standard base64.
      - `attestation` string, required: The CBOR attestation object from `attestKey(keyId, clientDataHash)`, standard base64, with `clientDataHash = SHA-256(proof.clientData bytes)` - the exact bytes sent as `proof.clientData`, so the attestation covers the passkey public key as well as the challenge.
    - object: Android Key Attestation.
      - `type` 'ANDROID_KEY_ATTESTATION', required
      - `certificateChain` string[], required: `KeyStore.getCertificateChain()` leaf first, each entry PEM or base64 DER. The key must have been generated with `setAttestationChallenge(challenge bytes)`.
  - `proof` object, required
    - `clientData` string, required: base64url JSON with exactly: `type` = `striga.passkey.import`, `challenge`, `challengeId`, `userId`, `credentialId`, `publicKeySha256` (base64url SHA-256 of the raw `publicKey` bytes as sent).
    - `signature` string, required: base64url DER ECDSA-SHA256 by the device key over the raw `clientData` bytes, produced behind the device biometric.

## Response `200`

The imported credential, an attestation summary and the refreshed MFA state.

- object
  - `credential` object, required
    - `id` string
    - `credentialId` string
    - `displayName` string
    - `name` string
    - `relyingPartyId` string
    - `transports` string[]
    - `insertInstant` integer
    - `lastUseInstant` integer, nullable
    - `userAgent` string, nullable
  - `attestation` object, required: `{ type: APP_ATTEST, environment }` or `{ type: ANDROID_KEY_ATTESTATION, securityLevel }`.
    - `type` string
    - `environment` string
    - `securityLevel` string
  - `multiFactorAuth` object, required
    - `enabled` boolean, required
    - `primaryMethod` 'totp' | 'passkey'
    - `enrolledMethods` string[], required
    - `passkeyCount` integer, required

## Other responses

- `400`: Request was rejected. The `errorCode` field disambiguates.
- `401`: Invalid API key or HMAC signature.

---

[API](https://skmtc.dev/striga/apis/striga-strong-customer-authentication.md) - [All operations](https://skmtc.dev/striga/apis/striga-strong-customer-authentication/llms.txt) - [OpenAPI document](https://skmtc.dev/striga/apis/striga-strong-customer-authentication/revisions/6a0cf1c67072?raw)
