SDK reference, iOS SDK / iOS Getting Started / Capture-Only Mode

Capture-Only Mode

Capture-Only mode presents Incode's capture UI and returns the captured media to your app without uploading that media to Incode for server-side processing. Use this mode when your app needs to control when and how captures are sent to your own back end.

The SDK still performs the local checks needed to guide capture, such as framing and image-quality checks. Server-side validation, OCR, face matching, and other processing do not run as part of the Capture-Only section.

Info

Capture-Only mode is not available in the Public distribution variant. If you set sdkMode to .captureOnly in that variant, the SDK logs a warning and uses .standard mode instead.


Supported Modules

Capture-Only mode supports these capture modules:

Module Builder Result callback
ID Capture addIdScan(...) onIdFrontCompleted(_:) and onIdBackCompleted(_:)
Selfie addSelfieScan(...) onSelfieScanCompleted(_:)
Document Capture addDocumentScan(...) onDocumentScanCompleted(_:)
Video Selfie addVideoSelfie(videoSelfieConfiguration:) onVideoSelfieCompleted(_:)

Do not add processing or non-capture modules to a Capture-Only flow. Note that:

  • addIdScan(scanStep: .both) normally runs Process ID automatically after ID Capture. In this mode, it does not; it adds ID Capture only.
  • The Process ID and NFC modules are not supported when sdkMode is .captureOnly.
  • You can add the Intro module, but it shows a local screen only; it returns no capture result.
  • Capture-Only mode does not support the Full Name, Phone, Email, Geolocation, or Signature modules.

Set sdkMode before creating an IncdOnboardingFlowConfiguration. Some builder behavior depends on the active SDK mode.


How It Works

Use the Sections API to run one capture section at a time. A module-specific callback returns the capture result. After the section closes, onOnboardingSectionCompleted(_:) reports its tag and the next section can start.

sequenceDiagram
    autonumber
    participant App as Host App
    participant SDK as Incode SDK
    participant Server as Customer Server

    App->>SDK: Initialize and set sdkMode = .captureOnly
    App->>SDK: startOnboardingSection(...)
    SDK->>SDK: Local checks and capture
    SDK-->>App: Module result callback
    SDK-->>App: onOnboardingSectionCompleted(sectionTag)
    App->>Server: Send captured media and metadata
    Server-->>App: Accept or request another capture
    App->>SDK: Start the next section when needed
    App->>SDK: finishFlow(...)
    SDK-->>App: finishFlow completion
    Note over App,SDK: Diagnostics delivery is asynchronous and<br/>is not ordered with the completion
    SDK-->>App: onDiagnosticsDataReceived(data), when enabled and available
    App->>SDK: deleteLocalUserData()

Only one section can run at a time. Starting another section before the active one completes reports IncdFlowError.sectionAlreadyRunning(activeSectionTag) to the delegate supplied for the rejected request. The active section continues running.

onOnboardingSectionCompleted(_:) is the safe point to start another section. Do not start the next section from a module result callback.


Set Up Capture-Only Mode

1. Initialize the SDK

Set the required presenting view controller, initialize the SDK, and then enable Capture-Only mode. The SDK manager is a singleton; the object that must remain available for presentation is your view controller because presentingViewController is a weak reference.

let manager = IncdOnboardingManager.shared
manager.presentingViewController = self

manager.initIncdOnboarding { success, _ in
    guard success == true else {
        // Handle IncdInitError.
        return
    }

    manager.sdkMode = .captureOnly
}

All initIncdOnboarding(...) configuration parameters are optional. Pass your normal url, apiKey, logging, or experiment settings when your integration requires them. Console logging is disabled by default; set loggingEnabled: true only when you need SDK console logs.

2. Build and Start a Capture Section

Create a separate IncdOnboardingFlowConfiguration for each section and pass your IncdOnboardingDelegate to startOnboardingSection(flowConfig:sectionTag:delegate:).

ID Capture

let flow = IncdOnboardingFlowConfiguration()
flow.addIdScan(scanStep: .both)

IncdOnboardingManager.shared.startOnboardingSection(
    flowConfig: flow,
    sectionTag: "id-capture",
    delegate: self
)

For .both, the same section captures the front and then the back of an ID. It calls onIdFrontCompleted(_:), then onIdBackCompleted(_:), and finally calls onOnboardingSectionCompleted("id-capture"). A passport has no back capture.

Key IdScanResult capture fields are:

  • image: UIImage?
  • base64Image: String?
  • encryptedBase64Image: String?
  • chosenIdType: IdType?
  • idCategory: IDCategory
  • metadata: String?, populated for Capture-Only captures
  • error: IncdIdScanError?

See ID Capture for all configuration options and result fields.

Selfie

let flow = IncdOnboardingFlowConfiguration()
flow.addSelfieScan()

IncdOnboardingManager.shared.startOnboardingSection(
    flowConfig: flow,
    sectionTag: "selfie",
    delegate: self
)

Key SelfieScanResult capture fields are:

  • image: UIImage?
  • selfieBase64: String?
  • selfieEncryptedBase64: String?
  • metadata: String?
  • frameWithDepth: String?, when that capture option is enabled and supported
  • videoFileURL: URL? and presignedVideoFileURL: URL?, when video capture is enabled
  • error: SelfieScanError?

Read URL-backed results before deleting local user data. See Selfie for all configuration options and result fields.

Document Capture

let flow = IncdOnboardingFlowConfiguration()
flow.addDocumentScan(
    documentType: .addressStatement,
    documentSources: [.camera, .photoUpload, .fileUpload]
)

IncdOnboardingManager.shared.startOnboardingSection(
    flowConfig: flow,
    sectionTag: "document-capture",
    delegate: self
)

Key DocumentScanResult capture fields are:

  • documentImage: UIImage?
  • data: Data?, containing image or PDF bytes when available
  • mimeType: String?
  • documentType: DocumentType
  • error: DocumentScanError?

Server-generated OCR result fields are not produced by a Capture-Only section. See Document Capture for all configuration options and result fields.

Video Selfie

let configuration = VideoSelfieConfiguration()
configuration.tutorials(enabled: true)
configuration.selfieScan(enabled: true)
configuration.idScan(enabled: true, validateId: false)

let flow = IncdOnboardingFlowConfiguration()
flow.addVideoSelfie(videoSelfieConfiguration: configuration)

IncdOnboardingManager.shared.startOnboardingSection(
    flowConfig: flow,
    sectionTag: "video-selfie",
    delegate: self
)

VideoSelfieResult can contain:

  • selfie: UIImage?
  • idFront: UIImage?, idBack: UIImage?, and passport: UIImage?
  • document: UIImage?
  • videoData: Data? and audioData: Data?
  • voiceConsentSelfie: UIImage?
  • error: VideoSelfieError?

The SDK delivers onVideoSelfieCompleted(_:) when the result contains recorded video or audio. A fatal Video Selfie failure is also reported through onError(_:). See Video Selfie for all configuration options and result fields.


Receive Results

Implement the callbacks for the modules you use. Copy or upload any result data your app needs before local cleanup.

extension MyCaptureViewController: IncdOnboardingDelegate {
    func onIdFrontCompleted(_ result: IdScanResult) {
        // Store or upload the front image and metadata.
    }

    func onIdBackCompleted(_ result: IdScanResult) {
        // Store or upload the back image and metadata.
    }

    func onSelfieScanCompleted(_ result: SelfieScanResult) {
        // Store or upload the selfie, metadata, and optional video data.
    }

    func onDocumentScanCompleted(_ result: DocumentScanResult) {
        // Store or upload result.data and result.mimeType.
    }

    func onVideoSelfieCompleted(_ result: VideoSelfieResult) {
        // Store or upload the captured Video Selfie assets.
    }

    func onOnboardingSectionCompleted(_ flowTag: String) {
        // The section is closed. Start the next section when your app is ready.
    }

    func onSuccess() {
        // Successful Sections API cleanup is reported by finishFlow's completion.
    }

    func onError(_ error: IncdFlowError) {
        if case .sectionAlreadyRunning = error {
            // The original section is still active. Do not delete its local data.
            return
        }

        // Persist any capture data you need, then remove SDK-local user data.
        IncdOnboardingManager.shared.deleteLocalUserData()
    }

    func userCancelledSession() {
        IncdOnboardingManager.shared.deleteLocalUserData()
    }
}

Forward Capture Metadata

When you submit ID or Selfie captures from your back end, include the corresponding metadata value from IdScanResult or SelfieScanResult. Forward it unchanged with the capture it describes. This metadata is part of the server-side capture-processing contract.


Finish the Capture-Only Session

After the final section completes successfully and your app has retained the capture data it needs, call finishFlow once:

IncdOnboardingManager.shared.finishFlow { success, error in
    guard success else {
        // Handle IncdError.
        return
    }

    IncdOnboardingManager.shared.deleteLocalUserData()
}

In Capture-Only mode, finishFlow performs local flow cleanup and does not call the back end. For a successful Sections API integration, its completion is the success signal; it does not call onSuccess().

Forward Diagnostics Data

When diagnostics are enabled, the SDK returns a prepared diagnostics payload through onDiagnosticsDataReceived(_:) instead of uploading it. The callback can arrive asynchronously after finishFlow completes. Terminal SDK errors and user cancellation also finalize diagnostics automatically.

func onDiagnosticsDataReceived(_ data: Data) {
    // Forward the prepared payload without modifying it.
}

Use the diagnostics upload contract provided for your Incode server integration. If sendDiagnosticsData is false, the SDK does not produce this callback.

Delete Local User Data

deleteLocalUserData() clears the current onboarding state and removes SDK-managed local capture files. Call it only after your app has copied or uploaded every capture it needs.

Use these terminal cleanup points:

  • The finishFlow completion after a successful Capture-Only Sections flow.
  • onError(_:) for a terminal flow error, excluding .sectionAlreadyRunning.
  • userCancelledSession() after cancellation.

Danger

Do not call deleteLocalUserData() while a section is active or before reading URL-backed capture results. Deleting local files is permanent.


Customization

Capture-Only mode uses the same theme, localization, tutorial, retake, and capture-quality options as the corresponding modules in other SDK modes. See Customization and each supported module page for the available settings.

For related integration guidance, see SDK Modes, Integration Approaches, and API Reference.