SDK reference, iOS SDK / iOS Individual Modules

ID Capture

The ID Capture module captures the front and back of a government-issued ID, with auto-capture and quality checks, and produces clean images for processing. It is the entry point for most identity-capture flows.

For an overview of this module and how it works, see ID Capture.

How you use this module depends on your integration pattern. When the app defines the steps in code, you add the module to an IncdOnboardingFlowConfiguration as shown below; when the flow is defined in Dashboard, you reference it by session token and let the back end drive the steps. See Integration Approaches.

Availability: All variants.

Add ID Capture

  1. Add the module with addIdScan().
    flowConfig.addIdScan(
        idType: nil,                              // .id or .passport; nil shows the chooser
        scanStep: .both,                          // .front / .back / .both
        idCategory: .primary,                     // .primary or .secondary
        showTutorials: true,
        showRetakeScreenForManualCapture: nil,
        showRetakeScreenForAutoCapture: nil,
        autocaptureUxMode: nil,                   // .countdown or .holdStill(duration:)
        showIDOverlay: nil,
        streamFrames: nil,
        initialOrientation: nil,
        enableAudioStream: nil,
        showIdTypeChooser: nil,
        digitalIdsUpload: false,
        motion: false                             // collect motion data + run local motion-behavior calc
    )
    
  2. Add ID OCR/Process after ID Capture. ID Capture captures the document images, and processing then runs validation and OCR on what was captured. When scanStep == .both, processing runs automatically afterward.

Configuration Options

Configure the module with addIdScan().

Option Type Description
idType IdType? Sets the type of document: .id or .passport. nil lets the user select.
scanStep ScanStep Selects which sides are captured: .front, .back, or .both. Default: .both.
idCategory IDCategory .primary or .secondary.
showTutorials Bool? Shows the tutorial screen that explains how to scan an ID.
showIdTypeChooser Bool? Shows or hides the document type selector.
showRetakeScreenForAutoCapture Bool? Controls whether the review/retake screen appears after auto-capture.
showRetakeScreenForManualCapture Bool? Controls whether the review/retake screen appears after manual capture.
autocaptureUxMode AutocaptureUxMode? Specifies auto-capture behavior: .countdown or .holdStill(duration:).
showIDOverlay Bool? Shows a visual overlay/frame guide on the camera preview to help the user align the ID within the capture area. Does not impact UI v2. Default: false.
streamFrames Bool? Turns on frame streaming during capture. Only applies for the Video Conference (-vc) variant.
initialOrientation IDOrientation? Sets the orientation of the on-screen ID template: .vertical or .horizontal. Does not impact device rotation.
enableAudioStream Bool? Enables audio capture alongside the video/image stream during scanning. Only applies if streamFrames is true.
digitalIdsUpload Bool? Allows uploading an existing digital ID (PDF).
motion Bool Enables motion data collection and local motion-behavior calculation for the capture. Default: false.

ID Capture ships both a v1 (legacy) and a v2 (smart-capture) screen. The one that runs is not controlled by an addIdScan parameter; the SDK selects it automatically based on a remote UX experiment and, for the v2 US smart-capture back-scan path, the active flow's Workflow configuration in Dashboard. Neither condition is directly settable by the integrating app.

There is also a non-UI variant for headless capture: addIdScanNonUI(scanStep:idCategory:streamFrames:enableAudioStream:).

The overload taking showRetakeScreen, showAutoCaptureRetakeScreen, and enableRotationOnRetakeScreen is deprecated. Use the renamed parameters shown above for showRetakeScreen (showRetakeScreenForManualCapture) and showAutoCaptureRetakeScreen (showRetakeScreenForAutoCapture). enableRotationOnRetakeScreen has no replacement; it was removed instead of renamed, and is retained in the deprecated overload only as an unused parameter.

Result

ID Capture reports through the IncdOnboardingDelegate callbacks, with one pair per captured side plus the lifecycle and per-attempt variants:

func onIdFrontStarted(_ controller: any IDCaptureController)
func onIdFrontCompleted(_ result: IdScanResult)
func onIdFrontAttemptCompleted(_ result: IdScanResult)
func onIdBackStarted(_ controller: any IDCaptureController)
func onIdBackCompleted(_ result: IdScanResult)
func onIdBackAttemptCompleted(_ result: IdScanResult)

onIdFrontCompleted(_:) and onIdBackCompleted(_:) each deliver an IdScanResult for that side. Check error first: a non-nil value indicates a fatal, pre-upload failure (for example, the scan never reached the server) and takes precedence over the other fields. Otherwise, treat scanStatus as the authoritative validation outcome for the capture. failReason provides supplementary detail and is not derived from scanStatus. Both are independently reported from the same upload response.

IdScanResult fields:

  • image: The captured ID or passport image.
  • base64Image: The Base64-encoded string of the captured image.
  • encryptedBase64Image: The encrypted Base64-encoded image, when encryption is enabled.
  • croppedFace: The face photo cropped from the ID; nil when no face can be extracted.
  • chosenIdType: The document type the user chose (IdType).
  • classifiedIdType: The document type returned by the server.
  • idCategory: Whether the document is a primary or secondary identification document (IDCategory: .primary or .secondary).
  • scanStatus: The scan outcome; an IncdIdScanStatus: unknown, ok, errorClassification, errorGlare, errorSharpness, errorReadability, errorInCapture, errorUnacceptableID, or wrongSide.
  • failReason: The reason the scan failed; nil when the scan succeeds.
  • issueName: The legal name on the document.
  • issueYear: The year the document was issued.
  • countryCode: The country code of the document's issuing country.
  • allAttemptsExhausted: true if all retry attempts are used up. This reflects the current side, front or back, independently.
  • metadata: The proprietary capture metadata required by the SDK; populated only in Capture-Only mode.
  • idealCaptureEnvironmentTestResult: The result of the ideal-capture-environment test.
  • deviceStats: A DeviceStats snapshot of the device state when the result was produced. Its motionStatus (MotionStatus) reports device motion during capture. MotionStatus is a CustomStringConvertible enum (not a string-backed/RawRepresentable enum) with three cases; its description, and the value serialized to the wire, is the uppercase string shown in parentheses:
    • .unclear"UNCLEAR": Motion could not be determined; this is the fallback used when no motion result is available.
    • .pass"PASS": Device motion was within acceptable limits.
    • .fail"FAIL": Excessive device motion was detected.
  • error: An IncdIdScanError when the capture failed; otherwise, nil.

Errors also surface through IncdOnboardingDelegate.onError(_:). There is no ID-Capture-specific delegate error callback beyond the per-result error field.

Real-Time Capture Feedback

IDFrameAnalysisError gains a new case:

case preparingCamera  // new

preparingCamera is reported through IDCaptureFeedback.error while the ID camera is getting ready, before the preview is shown. It is not an error: the capture continues normally once the camera is ready, and the SDK's own screens show the existing "Adjusting camera" copy for it, just like settingUpStreaming. If you switch exhaustively over IDFrameAnalysisError, add a handler for this case.

func idCaptureController(
    _ controller: any IDCaptureController,
    didReceive feedback: IDCaptureFeedback
) {
    if feedback.error == .preparingCamera {
        // The SDK is preparing the camera; no recovery action is required.
    }
}

Custom-UI Capture with IDCaptureController

For headless capture where your app draws its own camera UI, add the module with addIdScanNonUI(scanStep:idCategory:streamFrames:enableAudioStream:) and drive capture yourself through an IDCaptureController. The SDK hands you the controller in onIdFrontStarted(_:) and onIdBackStarted(_:); store it, set its delegate, and start it against a CALayer that hosts the camera preview.

func onIdFrontStarted(_ controller: any IDCaptureController) {
    self.idCaptureController = controller
    controller.delegate = self
    controller.start(
        in: cameraContainerView.layer,
        guidelineFrame: nil,   // nil uses the controller's internal default
        idType: nil,           // nil defaults to .id
        idOrientation: nil,    // nil defaults to .horizontal
        idSide: nil            // nil defaults to .front
    )
}

IDCaptureController is an ObservableObject, so its published state can back a SwiftUI view directly.

State

  • isManualCapture (Bool): Whether capture is currently in manual mode.
  • canUploadIDScan (Bool): Whether a captured scan is ready to upload.
  • remainingUploadAttempts (Int): Upload attempts left for the current side.
  • recommendedGuideline (CGRect?): The controller's recommended guideline frame, if computed.
  • isRunning (Bool): Whether the capture session is active.
  • currentIdType (IdType): The document type currently being captured.
  • currentIdSide (IDSide, get/set): The side being captured, .front or .back.
  • currentIdOrientation (IDOrientation): The detected orientation, .vertical or .horizontal.
  • currentFeedback (IDCaptureFeedback?): The latest real-time feedback.
  • currentGuidelineFrame (CGRect): The guideline frame currently in effect.

Lifecycle

func start(in layer: CALayer, guidelineFrame: CGRect?, idType: IdType?, idOrientation: IDOrientation?, idSide: IDSide?)
func pauseAnalyzing()
func continueAnalyzing()
func stop()
func cancel()
func finish()
  • start(in: guidelineFrame: idType: idOrientation: idSide:) attaches the camera preview to the layer in aspect-fill mode and begins analyzing frames, stopping when an ideal frame is captured.
  • pauseAnalyzing() and continueAnalyzing() pause and resume analysis while keeping the camera feed running.
  • stop() ends the session and detaches the preview.
  • cancel() returns an empty result for the module and proceeds to the next one when possible.
  • finish() completes the ID Scan module and advances to the next one if available; call it once you are ready to move on.

Manual Capture

func switchToManualCapture()
func switchToAutomaticCapture()
func triggerManualFrameCapture()

Call switchToManualCapture() to leave auto-capture, then triggerManualFrameCapture() to capture the current frame on demand. Call switchToAutomaticCapture() to return to auto-capture.

Unlike switchToManualCapture(), which takes effect immediately, switchToAutomaticCapture() only updates the mode: frame analysis. The auto-capture fallback timers restart when capture next resumes analysis. Pair it with a continueAnalyzing() call — calling it on a live capture without a following resume leaves analysis paused with no timer armed.

Guideline Frame

The guideline frame defines the expected position and size of the ID within the preview and feeds the position, size, and visibility checks. It is expressed in the preview layer's coordinate space, not the camera frame.

func updateGuidelineFrame(_ frame: CGRect)

Push a new frame whenever your overlay moves or resizes, using the same preview space passed to start(...). The controller also reports a recommended frame through idCaptureControllerDidChangeIdAttributes(...); you can apply it or supply your own.

Digital ID and Barcode

When you accept an existing digital document instead of a live scan, hand the data to the controller:

func setDigitalID(pdfDocumentData: Data)
func setBarcode(barcodeData: String)

Upload

func startUpload()                                                  // results via delegate
func upload(onProgressUpdate: ((Float) -> Void)?) async -> IDUploadResult

startUpload() reports results and progress through the delegate. upload(onProgressUpdate:) returns the result directly and reports progress through the optional closure. IDUploadResult is Result<Void, IDUploadError>.

Task {
    let result = await controller.upload { progress in
        updateProgressBar(progress)
    }
    switch result {
    case .success:
        handleUploadSuccess()
    case .failure(let error):
        handleUploadError(error)
    }
}

IDCaptureControllerDelegate

func idCaptureControllerDidStart(_ controller: any IDCaptureController)
func idCaptureControllerDidDetectID(_ controller: any IDCaptureController) async
func idCaptureController(_ controller: any IDCaptureController, didReceive feedback: IDCaptureFeedback)
func idCaptureControllerDidClearFeedback(_ controller: any IDCaptureController)
func idCaptureControllerDidChangeIdAttributes(_ controller: any IDCaptureController, orientation: IDOrientation, idType: IdType, recommendedGuidelineRect: CGRect?)
func idCaptureController(_ controller: any IDCaptureController, willCapturePhoto frame: UIImage, idCrop: UIImage) async
func idCaptureController(_ controller: any IDCaptureController, didCapturePhoto frame: UIImage, idCrop: UIImage) async
func idCaptureControllerDidSwitchToManualCapture(_ controller: any IDCaptureController)
func idCaptureControllerDidSwitchToAutomaticCapture(_ controller: any IDCaptureController)
func idCaptureController(_ controller: any IDCaptureController, captureAttemptInFlight: Bool)
func idCaptureController(_ controller: any IDCaptureController, didBecomeReadyToProceedWithId side: IDSide)
func idCaptureControllerDidCompleteIdScan(_ controller: any IDCaptureController)
func idCaptureController(_ controller: any IDCaptureController, didCaptureDigitalID: Data) async
func idCaptureController(_ controller: any IDCaptureController, didCaptureBarcode: String) async

// Uploading
func idCaptureControllerDidStartUploading(_ controller: any IDCaptureController)
func idCaptureController(_ controller: any IDCaptureController, didUpdateUploadProgress progress: Float)
func idCaptureController(_ controller: any IDCaptureController, didFinishUploading status: IDUploadResult, canRetry: Bool)

idCaptureControllerDidClearFeedback(_:), idCaptureControllerDidSwitchToAutomaticCapture(_:) and idCaptureController(_:captureAttemptInFlight:) have default no-op implementations, so existing conformers do not need to add them. idCaptureController(_:captureAttemptInFlight:) reports true when a capture attempt is claimed and false when it is released, so UI that starts or leaves a capture can follow it. Real-time feedback arrives as an IDCaptureFeedback, whose error is an IDFrameAnalysisError; see Real-Time Capture Feedback for the case set and handling.

Errors

IncdIdScanError cases:

  • error
  • cameraSetupFailed
  • permissionsDenied
  • fakePermissionsDenied
  • skipped
  • streamAudioPermissionsDenied

Was this page helpful?