The KYC Documents Capture SDK gives your organization full control over the document and selfie capture experience on iOS. You own the UI, the branding, and the flow — Trulioo powers the underlying image capture and verification technology.
This means you can build capture experiences that are fully aligned with your brand guidelines, with custom overlays, animations, and messaging, while relying on Trulioo's built-in verification intelligence for accuracy and security.
Looking for a ready-made solution?
If you prefer a complete Trulioo-designed UI out of the box, see the KYC Documents iOS SDK instead. It delivers the same trusted capture and verification technology with minimal setup.
Quick Start
- Add the
TruliooKYCDocumentsCaptureSwift package to your project. - Get a shortcode from your backend for the active transaction.
- Call
capture.initialize(shortcode:...)and wait for the completion callback. - Create a camera with
capture.createCamera(config:). - Call
camera.render(cameraProps:)and embed the returnedUIViewController. - Use
startFeedback(...)for auto capture orcaptureLatestFrame(...)for manual capture. - Call
verifyImage()thenacceptImage()on the capture result. - Call
capture.submit(...)when all required images have been accepted. - Call
capture.reset()when the flow is complete, then remove the cameraUIViewControllerfrom the hierarchy.
What This SDK Covers
This guide is for integrations where your application owns the screen and embeds the SDK camera UIViewController into your own UI. The SDK handles:
- Camera session setup and teardown
- Frame analysis for auto capture
- Post-capture image verification
- Accepted-image association with the active transaction
Your application handles:
- The UIKit container and surrounding controls
- Camera permission prompts and user guidance
- Whether the current step is document or selfie capture
- Whether to use auto capture or manual capture
- Whether to accept or retake a verified image
- When to submit or reset the session
Package and Compatibility
| Property | Value |
|---|---|
| GitHub repository | https://github.com/Trulioo/kyc-documents-capture.git |
| Package name | TruliooKYCDocumentsCapture |
| Main runtime wrapper | TruliooCaptureRuntimeLive |
| Minimum iOS version | 15.0 |
| Distribution | Swift Package Manager |
The SwiftPM package includes the binary TruliooKYCDocumentsCapture target, the TruliooKYCDocumentsCaptureRuntime Swift bridge, and the upstream Trulioo dependency pinned by the package release metadata.
Installation
Add the package to your Package.swift:
dependencies: [
.package(url: "https://github.com/Trulioo/kyc-documents-capture.git", from: "X.Y.Z")
]For beta builds, pin to the prerelease tag explicitly:
dependencies: [
.package(url: "https://github.com/Trulioo/kyc-documents-capture.git", exact: "X.Y.Z-beta.N")
]Link the products to your target:
.target(
name: "YourApp",
dependencies: [
.product(name: "TruliooKYCDocumentsCapture", package: "kyc-documents-capture"),
.product(name: "TruliooKYCDocumentsCaptureRuntime", package: "kyc-documents-capture"),
]
)Import the modules you need:
import TruliooKYCDocumentsCapture
import TruliooKYCDocumentsCaptureRuntimeImport Trulioo only if your host application also uses the base Trulioo SDK directly.
End-to-End Example
import TruliooKYCDocumentsCapture
import TruliooKYCDocumentsCaptureRuntime
import UIKit
final class CaptureHostViewController: UIViewController {
private let capture = TruliooCaptureRuntimeLive()
@IBOutlet private weak var cameraContainer: UIView!
private var cameraViewController: UIViewController?
func startCapture(shortcode: String) {
capture.initialize(
shortcode: shortcode,
options: TruliooCaptureInitializationOptions()
) { [weak self] error, transactionId in
guard let self else { return }
guard error == nil else {
print("Initialize failed:", error!)
return
}
print("Initialized transaction:", transactionId ?? "missing")
let camera = self.capture.createCamera(
config: ContractTruliooCameraConfig(detectionType: .document)
)
let controller = camera.render(cameraProps: nil)
self.embedCameraController(controller)
self.cameraViewController = controller
camera.startFeedback { error, response in
guard error == nil, let response else {
print("Auto capture failed:", error!)
return
}
Task {
do {
let verify = try await response.verifyImage()
let accepted = verify.verifyResponses.contains { value in
value == "SUCCESS" || value == "SUCCESS_REQUIRES_BACK"
}
if accepted {
try await response.acceptImage()
}
self.capture.submit { submitError in
if let submitError {
print("Submit failed:", submitError)
return
}
self.capture.reset()
self.removeCameraController()
}
} catch {
print("Verify or accept failed:", error)
}
}
}
}
}
private func embedCameraController(_ controller: UIViewController) {
addChild(controller)
controller.view.translatesAutoresizingMaskIntoConstraints = false
cameraContainer.addSubview(controller.view)
NSLayoutConstraint.activate([
controller.view.leadingAnchor.constraint(equalTo: cameraContainer.leadingAnchor),
controller.view.trailingAnchor.constraint(equalTo: cameraContainer.trailingAnchor),
controller.view.topAnchor.constraint(equalTo: cameraContainer.topAnchor),
controller.view.bottomAnchor.constraint(equalTo: cameraContainer.bottomAnchor),
])
controller.didMove(toParent: self)
}
private func removeCameraController() {
guard let controller = cameraViewController else { return }
controller.willMove(toParent: nil)
controller.view.removeFromSuperview()
controller.removeFromParent()
cameraViewController = nil
}
}Initialization
Call capture.initialize(shortcode:options:completion:) before creating cameras or submitting the transaction. It configures the Capture runtime, authorizes the session, fetches configuration, and returns the transaction ID through the completion callback.
let capture = TruliooCaptureRuntimeLive()
capture.initialize(
shortcode: shortcode,
options: TruliooCaptureInitializationOptions()
) { error, transactionId in
guard error == nil else {
print("Initialization failed:", error!)
return
}
print("Active transaction:", transactionId ?? "missing")
}Always initialize with a shortcode created for the active transaction. Do not reuse a stale shortcode after reset(). After calling reset(), a new initialize(...) call is required before reusing the runtime.
Superseded initializations: If a newer initialize(...) call supersedes an older one, the older completion receives TruliooCaptureInitializationSupersededError. This is not a fatal product failure — handle it as an expected race condition if your app can trigger multiple initializations.
Creating and Rendering a Camera
Use capture.createCamera(config:) to create a camera. Document capture is the default detection type.
// Document camera (default)
let documentCamera = capture.createCamera(
config: ContractTruliooCameraConfig(detectionType: .document)
)
// Selfie camera
let selfieCamera = capture.createCamera(
config: ContractTruliooCameraConfig(detectionType: .biometricSelfie)
)render(cameraProps:) returns an SDK-owned UIViewController. Embed it in your view hierarchy using standard UIKit child view controller containment:
let controller = camera.render(cameraProps: nil)
addChild(controller)
controller.view.translatesAutoresizingMaskIntoConstraints = false
cameraContainer.addSubview(controller.view)
NSLayoutConstraint.activate([
controller.view.leadingAnchor.constraint(equalTo: cameraContainer.leadingAnchor),
controller.view.trailingAnchor.constraint(equalTo: cameraContainer.trailingAnchor),
controller.view.topAnchor.constraint(equalTo: cameraContainer.topAnchor),
controller.view.bottomAnchor.constraint(equalTo: cameraContainer.bottomAnchor),
])
controller.didMove(toParent: self)For SwiftUI, bridge the controller using UIViewControllerRepresentable.
To inspect the active stream resolution after startup:
camera.getResolution { resolution in
print("Resolution: \(resolution.width) x \(resolution.height)")
}Removing the camera controller — call this when replacing the camera, dismissing the capture flow, or resetting the session:
controller.willMove(toParent: nil)
controller.view.removeFromSuperview()
controller.removeFromParent()Also call camera.remove() so the SDK can release camera resources:
camera.remove()Capturing an Image
Auto Capture (Recommended)
Call startFeedback(...) to let the SDK process frames until it captures an acceptable image:
camera.startFeedback { error, response in
guard error == nil, let response else {
print("Auto capture failed:", error!)
return
}
print("Captured image:", response.imageId)
print("Detection type:", response.detectionType)
print("Feedback:", response.imageFeedbacks)
}To stop an active auto-capture session:
camera.stopFeedback()To apply your own acceptance rule while still using SDK frame analysis:
camera.startFeedback(
filter: { feedback in
feedback.imageFeedbacks.contains("SUCCESS")
},
result: { error, response in
guard error == nil, let response else { return }
print("Captured filtered image:", response.imageId)
}
)Manual Capture
Use captureLatestFrame(...) to trigger a manual capture instead of waiting for auto capture:
camera.captureLatestFrame { error, response in
guard error == nil, let response else {
print("Manual capture failed:", error!)
return
}
print("Manually captured image:", response.imageId)
}Verifying and Accepting an Image
After capturing, call verifyImage() to get post-capture feedback. Then call acceptImage() to mark the image as accepted for the transaction.
verifyImage() does not finalize the transaction — it gives your application the information to decide whether to keep or retake the image.
Task {
do {
let verify = try await response.verifyImage()
if !verify.isVerifyAttemptAvailable {
print("No further verify attempts available.")
}
print("Verify responses:", verify.verifyResponses)
let accepted = verify.verifyResponses.contains { value in
value == "SUCCESS" || value == "SUCCESS_REQUIRES_BACK"
}
if accepted {
try await response.acceptImage()
print("Image accepted.")
}
} catch {
print("Verify or accept failed:", error)
}
}Verify attempts are limited. Check isVerifyAttemptAvailable before calling verifyImage() again. Avoid repeated verify calls once this value is false.
Recommended acceptance rule: treat SUCCESS and SUCCESS_REQUIRES_BACK as accepted outcomes for image upload.
Submitting and Resetting the Session
Call capture.submit(...) after all required images have been accepted:
capture.submit { error in
if let error {
print("Submit failed:", error)
return
}
print("Submission complete.")
}Then call capture.reset() and remove the camera view controller from the hierarchy:
capture.reset()
removeCameraController()submit(...) finalizes the active transaction but does not clear local Capture state. Always call reset() and remove the embedded camera controller when the flow is done or abandoned.
Observing Camera State
Use onFeedbackState() to observe SDK feedback labels during capture:
camera.onFeedbackState().collect { state in
print("Feedback state:", state)
}Use onCaptureRegion() to observe the active capture region for custom overlays:
camera.onCaptureRegion().collect { region in
print("Capture region:", region)
}Call camera.resume() to resume preview after an interruption or capture review:
camera.resume()Result Reference
| Method | Returns | Key fields |
|---|---|---|
startFeedback(...) | TruliooCaptureResponse | imageId, detectionType, imageFeedbacks |
startFeedback(filter:result:) | TruliooCaptureResponse | imageId, detectionType, imageFeedbacks |
captureLatestFrame(...) | TruliooManualCaptureResponse | imageId, detectionType |
verifyImage() | ITruliooVerifyFeedback | isVerifyAttemptAvailable, verifyResponses |
Public API Reference
Session and submission:
TruliooCaptureRuntimeLive.initialize(shortcode:options:completion:) · TruliooCaptureRuntimeLive.submit(completion:) · TruliooCaptureRuntimeLive.reset()
Camera creation:
TruliooCaptureRuntimeLive.createCamera(config:) · ContractTruliooCameraConfig · DetectionType
Camera operations:
render(cameraProps:) · startFeedback(...) · startFeedback(filter:result:) · captureLatestFrame(...) · stopFeedback() · onFeedbackState() · onCaptureRegion() · getResolution(...) · resume() · remove()
Capture result operations:
verifyImage() · acceptImage()
Common Mistakes
Creating a camera before initialization completes. The initialize(...) completion must succeed before calling createCamera(config:).
Not retaining the runtime instance. TruliooCaptureRuntimeLive must be retained for the duration of the capture flow. If it is deallocated early, callbacks and async verify work will not complete.
Not removing the camera UIViewController from the hierarchy. After reset(), always follow the three-step teardown: willMove(toParent: nil) → view.removeFromSuperview() → removeFromParent(). Also call camera.remove() to release SDK camera resources.
Assuming submit(...) also clears local state. Always call reset() after submitting or abandoning a flow.
Treating a superseded initialization as a fatal error. TruliooCaptureInitializationSupersededError means a newer initialization took over. Handle it as an expected race condition, not a product failure.
Calling verifyImage() after isVerifyAttemptAvailable is false. Verify attempts are limited — check this value before making additional verify calls.
Troubleshooting
Initialization fails: Confirm the shortcode is valid and belongs to the expected environment.
Camera view stays blank: Confirm the returned UIViewController is retained, embedded in the active view hierarchy using the correct containment pattern, and that camera permission has been granted.
Auto capture never resolves: Use onFeedbackState() to inspect whether the SDK is repeatedly requesting a retake condition. This usually indicates a lighting or framing issue.
Submit fails after capture: Confirm all required images were accepted before calling submit(...).
Diagnostic Checklist
When filing a support issue, include:
- Capture SDK version.
- iOS version and device model.
- Host presentation mode (UIKit or SwiftUI bridge).
- Whether the flow was document or selfie capture.
- Whether the issue occurred during auto capture or manual capture.
- The transaction ID, if available.
- The latest feedback state or verify responses.
- Which stage the failure occurred at: initialize, render, capture, verify, accept, or submit.

