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

Capture-Only End-to-End Integration

Deepsight is Incode's liveness technology. During capture, the device collects extra signals: depth data, a short selfie video, and motion data. The back end uses these signals to find spoofing attempts.

This page shows how to use Deepsight with Capture-Only Mode. In Capture-Only Mode, the iOS SDK captures the images and signals but does not send them to Incode; your integration sends them with the Omni API. Use this mode when your app needs to control when and how captures are sent to your own back end. This page gives the SDK configuration, the order of the API calls, and a sample request for each call, for two flows:

  • Basic flow: An ID Capture module and a Selfie module. The user scans an ID and takes a selfie with Deepsight.
  • Video Selfie flow: The basic flow plus a Video Selfie module. The user records a video that can hold a second ID scan, a selfie, and voice consent. The back end compares this recording with earlier captures.

You can also run these flows without Deepsight.


Prerequisites

Before you start, ensure you have:

  • The iOS SDK set to .captureOnly. See Capture-Only Mode.
  • iOS SDK 5.45.0 or later. Earlier versions do not have DeepsightConfiguration or the motion parameter of addIdScan(...).
  • A Dashboard flow (configurationId) with:
    • Deepsight turned on in its Settings tab.
    • The Deepsight settings of the Face Capture module configured to match the SDK modality.
    • Face Capture and ID Capture modules with Motion set to match the SDK motion.
  • An API key for your environment.

Warning

Set the Deepsight and Motion options in the SDK to the same values as the flow configuration in Dashboard. The two control different things: Dashboard tells the back end which signals to expect, and the SDK configuration decides which signals the device collects. If they differ, the back end rejects the call with Invalid capture metadata. The next section gives the two settings.

The examples use YOUR_API_URL and YOUR_API_KEY, the same names as the installation examples. Replace them with the API URL and the API key of your environment.

Warning

Make these calls from your back end. The API key gives full access to your environment. Do not put the API key in the app, and do not call the Omni API from the device. The app sends the captures to your back end, and your back end makes the calls on this page.

Every call to YOUR_API_URL after omni/start needs these headers. An upload to a pre-signed URL does not use them.

Header Value
x-api-key YOUR_API_KEY, the API key of your environment.
api-version 1.0
X-Incode-Hardware-Id The token value that omni/start returned.
Content-Type application/json

SDK Configuration for Deepsight

Deepsight needs two changes in the SDK code, one for the ID Capture module and one for the Selfie module.

ID Capture

The motion parameter of addIdScan(...) is available in iOS SDK 5.45.0 and newer. It writes the motion flag into the capture metadata. The default value is false.

Set the same value as the Motion option of the ID Capture module in Dashboard. If the two differ, the back end rejects omni/add/front-id/v2 with Invalid capture metadata.

let flowConfig = IncdOnboardingFlowConfiguration()
flowConfig.addIdScan(
    idType: .id,
    showIdTypeChooser: false,
    motion: true
)

Selfie

The addSelfieScan(deepsight:) overload is available in iOS SDK 5.45.0 and newer. It replaces addSelfieScan(requireDepthData:videoLivenessRecording:), which is deprecated. See the Migration Guide.

DeepsightConfiguration has three independent fields:

Field Default Description
enabled true Turns Deepsight on or off. When false, the capture behaves as a plain single frame and the SDK ignores the modality.
modality .singleFrameWithDepth The capture mode. It takes effect only when enabled is true.
motion false Turns motion collection on or off. This field is independent of the other two.

The three modality values are:

Modality Depth Data Video Recording
.singleFrame No No
.singleFrameWithDepth Yes No
.singleFrameWithDepthAndVideo Yes Yes
flowConfig.addSelfieScan(
    deepsight: DeepsightConfiguration(
        enabled: true,
        modality: .singleFrameWithDepthAndVideo,
        motion: true
    )
)

Result Fields

Read these fields from the capture results and send them to the API:

Callback Result Field Send It To
onIdFrontCompleted(_:) IdScanResult base64Image, metadata omni/add/front-id/v2
onIdBackCompleted(_:) IdScanResult base64Image, metadata omni/add/back-id/v2
onSelfieScanCompleted(_:) SelfieScanResult frameWithDepth, metadata omni/add/face/third-party
onSelfieScanCompleted(_:) SelfieScanResult presignedVideoFileURL omni/recordings/import
onVideoSelfieCompleted(_:) VideoSelfieResult videoData The URL from omni/generateVideoSelfieUrl
onVideoSelfieCompleted(_:) VideoSelfieResult idFront omni/videoselfie/compare-id/v2, omni/videoselfie/compare-ocr/v2
onVideoSelfieCompleted(_:) VideoSelfieResult idBack omni/videoselfie/compare-back-id/v2, omni/videoselfie/compare-back-ocr/v2
onVideoSelfieCompleted(_:) VideoSelfieResult passport omni/videoselfie/compare-id/v2, omni/videoselfie/compare-ocr/v2

SelfieScanResult.frameWithDepth holds the frame with depth data. A device with no TrueDepth camera gives nil. In that case, send SelfieScanResult.selfieBase64, which the same callback returns and which holds the frame without depth data.

SelfieScanResult.presignedVideoFileURL is set in .captureOnly only, and only when the modality is .singleFrameWithDepthAndVideo.

VideoSelfieResult gives each image as a UIImage and each recording as Data. Encode each image as base64 before you send it.


Basic Flow

Use this flow for Deepsight with an ID Capture module and a Selfie module.

Call the endpoints in this order. A tag shows which capture module a step belongs to. Make a tagged step only when that module is in the flow. A step with no tag always applies.

  1. POST /omni/start
  2. POST /omni/add/device-fingerprint
  3. ID Capture, front side: POST /omni/add/front-id/v2?onlyFront=false

    Warning

The onlyFront query parameter tells the back end whether a back image follows.

  • To scan both sides, use addIdScan(scanStep: .both), send onlyFront=false, and make step 4.
  • To scan the front only, use addIdScan(scanStep: .front), send onlyFront=true, and omit step 4.

.both is the default.

4. **ID Capture, back side**: `POST /omni/add/back-id/v2` 5. **ID Capture**: `POST /omni/process/id` 6. **Selfie with Deepsight video**: `POST /omni/recordings/import` 7. **Selfie**: `POST /omni/add/face/third-party?imageType=selfie&recordingId=RECORDING_ID` 8. **Selfie**: `POST /omni/process/face?imageType=selfie` 9. `POST /omni/finish-status`

Info

Send the device fingerprint with omni/add/device-fingerprint directly after omni/start, before any capture. The SDK does the same in .standard mode: it sends the device fingerprint as part of the session setup, before the first module starts. Send it once for each session.

Which Steps Apply

Each tagged step needs its module in the flow and its data in the result.

Steps Make Them When Flow Configuration Result Field
3 The flow scans the front of a document addIdScan(...) IdScanResult.base64Image from onIdFrontCompleted(_:)
4 The flow scans the back of a document addIdScan(scanStep: .both), the default IdScanResult.base64Image from onIdBackCompleted(_:)
5 The flow scans a document addIdScan(...) None; this call carries no image
6 The selfie records a video for Deepsight addSelfieScan(deepsight:) with .singleFrameWithDepthAndVideo SelfieScanResult.presignedVideoFileURL
7, 8 The flow takes a selfie addSelfieScan(...) SelfieScanResult.frameWithDepth

Warning

A nil field is not always a skipped step. Your configuration decides which calls to make. The result field tells you what to send.

If you did not enable a step, its field is nil and you omit its calls. That is the normal case.

If you did enable a step and its field is nil, the capture did not complete. Do not treat this as a skipped step. Record the fault and examine it. The session continues without that check, and no error tells you so.

The SDK gives you the reason for Deepsight video. When presignedVideoFileURL is nil but the face capture completed, read SelfieScanResult.videoRecordingError. It reports insufficientStorageForVideoRecording or videoRecordingFailed.

Step 1: Start the Session

omni/start creates the interview. The response gives the token value for the X-Incode-Hardware-Id header of every later call.

curl --location 'YOUR_API_URL/omni/start' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: YOUR_API_KEY' \
--data '{
    "configurationId": "YOUR_FLOW_ID"
}'

Step 2: Add the Device Fingerprint

Read the fingerprint from the SDK with IncdOnboardingManager.shared.getDeviceFingerprint(). DeviceFingerprint is Encodable. Encode it as JSON and send it without changes.

let fingerprint = IncdOnboardingManager.shared.getDeviceFingerprint()
let fingerprintJson = String(data: try JSONEncoder().encode(fingerprint), encoding: .utf8)
curl --location 'YOUR_API_URL/omni/add/device-fingerprint' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN' \
--data "$FINGERPRINT_JSON"

The body has this shape. The values change with the device and the SDK version, so do not copy them.

{
    "sdkVersion": "5.45.0",
    "hash": "1234-1234-1234",
    "ip": "10.11.11.11",
    "osVersion": "18.1.1",
    "deviceModel": "iPhone16,2",
    "data": "{\"crossApp\":true}",
    "deviceType": "IOS",
    "algorithmName": "identifierForVendor",
    "algorithmVersion": "1.0.0"
}

Step 3: Add the Front of the ID

Send base64Image and metadata from the IdScanResult that onIdFrontCompleted(_:) returns.

curl --location 'YOUR_API_URL/omni/add/front-id/v2?onlyFront=false' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN' \
--data '{
    "base64Image": "ID_FRONT_BASE64_IMAGE",
    "metadata": "ID_FRONT_METADATA"
}'

Step 4: Add the Back of the ID

Send base64Image and metadata from the IdScanResult that onIdBackCompleted(_:) returns.

curl --location 'YOUR_API_URL/omni/add/back-id/v2' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN' \
--data '{
    "base64Image": "ID_BACK_BASE64_IMAGE",
    "metadata": "ID_BACK_METADATA"
}'

Step 5: Process the ID

curl --location 'YOUR_API_URL/omni/process/id' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN' \
--data '{}'

Step 6: Send the Deepsight Video

SelfieScanResult.presignedVideoFileURL is a file URL, not the video itself. Read that file as plain text. Then send the text in the video field without a change. The contents are already base64. Do not encode them again.

If presignedVideoFileURL is nil, there is no recording to send. Omit this step, and send the selfie without a recordingId.

curl --location 'YOUR_API_URL/omni/recordings/import' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN' \
--data '{
    "type": "selfie",
    "video": "CONTENTS_OF_PRESIGNED_VIDEO_FILE"
}'

The response gives a recordingId. Keep this value for the next call.

Step 7: Send the Selfie Image

Send frameWithDepth and metadata from the SelfieScanResult that onSelfieScanCompleted(_:) returns. Add the recordingId from the previous step as a query parameter.

curl --location 'YOUR_API_URL/omni/add/face/third-party?imageType=selfie&recordingId=RECORDING_ID' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN' \
--data '{
    "base64Image": "FRAME_WITH_DEPTH",
    "metadata": "SELFIE_METADATA"
}'

Step 8: Process the Selfie Image

This call runs the face checks on the selfie from step 7. The request body is empty.

curl --location 'YOUR_API_URL/omni/process/face?imageType=selfie' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN' \
--data '{}'

Step 9: Finish the Session

This call closes the session. Make it after all the capture and process calls. The request body is empty.

curl --location 'YOUR_API_URL/omni/finish-status' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN' \
--data '{}'

Video Selfie Flow

Use this flow for Deepsight with an ID Capture module, a Selfie module, and a Video Selfie module.

Call the endpoints in this order. A tag shows which capture module a step belongs to. Make a tagged step only when that module is in the flow. A step with no tag always applies.

  1. POST /omni/start
  2. POST /omni/add/device-fingerprint
  3. ID Capture, front side: POST /omni/add/front-id/v2?onlyFront=false
  4. ID Capture, back side: POST /omni/add/back-id/v2
  5. ID Capture: POST /omni/process/id
  6. Selfie with Deepsight video: POST /omni/recordings/import
  7. Selfie: POST /omni/add/face/third-party?imageType=selfie&recordingId=RECORDING_ID
  8. Selfie: POST /omni/process/face?imageType=selfie
  9. Video Selfie, document front side: POST /omni/videoselfie/compare-id/v2
  10. Video Selfie, document front side: POST /omni/videoselfie/compare-ocr/v2
  11. Video Selfie, document back side: POST /omni/videoselfie/compare-back-id/v2
  12. Video Selfie, document back side: POST /omni/videoselfie/compare-back-ocr/v2
  13. Video Selfie, taking a selfie: POST /omni/add/face/third-party?imageType=videoSelfie
  14. Video Selfie, taking a selfie: POST /omni/process/face?imageType=videoSelfie
  15. Video Selfie, audio recording, speech check: POST /omni/add/speech/v2
  16. Video Selfie, consent, face check: POST /omni/videoselfie/voiceconsent/add/face
  17. GET /omni/generateVideoSelfieUrl
  18. PUT the Video Selfie recording to the returned URL
  19. POST /omni/finish-status

The questions and the consent phrase come from a call you make before the module starts. It is not part of this sequence. See Get the Questions and the Consent Phrase.

Warning

Voice consent for the record needs no API call. Steps 15 and 16 ask the back end to check the consent. They are not what records it. The Video Selfie recording already holds the audio and the video of the user who speaks the phrase, and steps 17 and 18 send that recording.

If you collect the consent as a record only, omit steps 15 and 16. Omit them even when voiceConsent(enabled: true) is set. Make step 15 to check the words the user said. Make step 16 to match the face of the user during the consent.

Steps 1 through 8 are the same as the Basic Flow. Step 19 is step 9 in the Basic Flow.

Which Steps Apply

Each tagged step needs its Video Selfie step to have run and put its data in the result. Steps 17 and 18 are not tagged because Video Selfie always records.

Steps Make Them When Configuration Default Result Field
9, 10 Video Selfie scans the front of the document idScan(enabled: true) on idFront
11, 12 Video Selfie scans the back of the document idScan(enabled: true) on idBack
13, 14 Video Selfie takes a selfie selfieScan(enabled: true) on selfie
15 The recording has audio and you want the back end to check what the user said voiceConsent(enabled: true) or randomQuestions(enabled: true) off audioData
16 The consent has a face frame and you want the back end to match that face voiceConsent(faceRecognition: true) off voiceConsentSelfie

Info

Make the comparison calls, then send the recording last. This is the order the SDK uses in .standard mode: it compares the documents during the session, then sends the recording in a final upload step.

Make this call only if you want the back end to check the words the user said.

Make it before the Video Selfie module starts. It returns the question and answer pairs of your API key. Give that text to the SDK with randomQuestions(...) and voiceConsent(...), so the user reads the same text that the back end scores in step 15. If the text on screen is not the same text, the check fails for a user who said the phrase correctly.

In .captureOnly, the SDK does not make this call. It shows only the text you give it. In .standard mode, the SDK makes the call itself at the start of the step and shows what it returns.

curl --location 'YOUR_API_URL/omni/get/questionAndAnswer?numberOfQuestions=1&returnVoiceConsentQuestion=true' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN'

The response gives a list of questions. The entry with id 0 is the consent phrase.

Steps 9 through 12: Compare the Documents

The comparison calls use the ID frames that the Video Selfie module captured, not the frames from steps 3 and 4. VideoSelfieResult gives each frame as a UIImage. Encode it as base64 before you send it.

Step Endpoint Image
9 omni/videoselfie/compare-id/v2 idFront
10 omni/videoselfie/compare-ocr/v2 idFront
11 omni/videoselfie/compare-back-id/v2 idBack
12 omni/videoselfie/compare-back-ocr/v2 idBack

Send each pair of calls only when the matching frame is in the result:

  • If idFront is set, make steps 9 and 10.
  • If idBack is set, make steps 11 and 12.
  • If a field is nil, omit the two steps for that side. If the Video Selfie scanned that side, a nil value means the capture failed.

This is the same rule the SDK applies in .standard mode. There, the SDK gives the Video Selfie one scan step for each side of the document, then compares each side as it captures it.

Info

A passport has one side. The Video Selfie module makes a single scan step for it and puts the frame in VideoSelfieResult.passport.

Send passport to the step 9 and step 10 endpoints, and omit steps 11 and 12. The back end treats a passport the same as the front of a document.

Step 9: Compare the ID

This call makes sure that the document in the Video Selfie matches the document from step 3.

curl --location 'YOUR_API_URL/omni/videoselfie/compare-id/v2' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN' \
--data '{
    "base64Image": "ID_FRONT_BASE64_IMAGE"
}'

Response (200):

{
    "typeOfId": "DriversLicense",
    "idTypeMatched": true,
    "issueYear": 2017,
    "faceRecognitionConfidence": 1.0,
    "idSideMatched": true
}

Response (400):

{
    "timestamp": 1775238442064,
    "status": 4020,
    "error": "Image to compare not found.",
    "message": "BadRequestException: Unable to compare images - template not found",
    "path": "/omni/videoselfie/compare-id/v2"
}
Error Code Description
1003 Face cropping failure.
4019 Face not found.
4020 Image to compare not found.
5003 Unsatisfied image size.
5004 Document alignment failed.
5005 Document not found.

Step 10: Compare the OCR Data

This call makes sure that the data on the document in the Video Selfie is the same as the data from step 3.

curl --location 'YOUR_API_URL/omni/videoselfie/compare-ocr/v2' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN' \
--data '{
    "base64Image": "ID_FRONT_BASE64_IMAGE"
}'

Response (200):

{
    "success": true
}

A 400 response has the same shape as step 9. The error codes are 5003, 5004, and 5005.

Steps 11 and 12: Compare the Back of the Document

These two calls are the back-side counterparts of steps 9 and 10. Send the base64 of VideoSelfieResult.idBack.

curl --location 'YOUR_API_URL/omni/videoselfie/compare-back-id/v2' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN' \
--data '{
    "base64Image": "ID_BACK_BASE64_IMAGE"
}'
curl --location 'YOUR_API_URL/omni/videoselfie/compare-back-ocr/v2' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN' \
--data '{
    "base64Image": "ID_BACK_BASE64_IMAGE"
}'

The responses have the same shape as steps 9 and 10.

Steps 13 and 14: Send the Video Selfie Image

These two calls are optional. Make them when selfie is set in the Video Selfie module.

Steps 13 and 14 use the same two endpoints as steps 7 and 8, but they send a different image. Step 7 sends the image from the Selfie module. Step 13 sends the image that the Video Selfie module captured during the video. The imageType query parameter tells the back end which image it receives.

Steps Module Image imageType
7, 8 Selfie SelfieScanResult.frameWithDepth selfie
13, 14 Video Selfie VideoSelfieResult.selfie videoSelfie
curl --location 'YOUR_API_URL/omni/add/face/third-party?imageType=videoSelfie' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN' \
--data '{
    "base64Image": "VIDEO_SELFIE_BASE64_IMAGE"
}'

Step 14 processes that frame. The response gives a confidence value. Keep it for step 16.

curl --location 'YOUR_API_URL/omni/process/face?imageType=videoSelfie' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN' \
--data '{}'

Response (200):

{
    "confidence": 1
}

This call is optional. Make it to check the words the user said. It requires audioData to be set.

Omit it if you collect the consent as a record only. The recording in steps 17 and 18 holds the audio either way.

Before the module starts, get the questions and the consent phrase and give them to the SDK. See Get the Questions and the Consent Phrase.

Step 15 sends VideoSelfieResult.audioData as base64 in the base64Media field. Speech recognition runs inside this call. There is no process step after it.

curl --location 'YOUR_API_URL/omni/add/speech/v2' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN' \
--data '{
    "base64Media": "AUDIO_BASE64_DATA",
    "audioOnly": true
}'

Warning

The Android SDK uses a different speech endpoint. The iOS SDK calls omni/add/speech/v2 with a JSON body. The Android SDK calls omni/add/speech?audioOnly=true with the audio file as the request body. Use the iOS form on this page. If you write one back end for both platforms, make it accept the shape each platform sends.

This call is optional. Make it to match the face of the user during the consent. It requires voiceConsentSelfie to be set.

Omit it if you do not want this second face check. The consent is still recorded.

Send VideoSelfieResult.voiceConsentSelfie as base64, with the confidence value from step 14. The threshold is the bar the score is measured against. The SDK sends the recognition threshold, which you set with IncdOnboardingManager.shared.recognitionThreshold. The default is 0.6.

curl --location 'YOUR_API_URL/omni/videoselfie/voiceconsent/add/face' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN' \
--data '{
    "base64Image": "VOICE_CONSENT_SELFIE_BASE64_IMAGE",
    "confidence": 1,
    "threshold": 0.6
}'

Step 17: Get the Upload URL

This call reserves a pre-signed Amazon S3 URL for the Video Selfie recording. Make it after the comparison and face calls, then send the recording to that URL in step 18.

curl --location 'YOUR_API_URL/omni/generateVideoSelfieUrl' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN'

The response gives the URL for the recording:

{
    "url": "PRESIGNED_UPLOAD_URL"
}

Step 18: Send the Recording

Send VideoSelfieResult.videoData to the url from step 17.

curl --location --request PUT 'PRESIGNED_UPLOAD_URL' \
--header 'Content-Type: video/mp4' \
--data-binary '@/path/to/videoSelfie.mp4'

To send the recording in parts instead of one request, read Alternative: Upload the Video Selfie in Parts.


Alternative: Upload the Video Selfie in Parts

The Video Selfie flow sends the recording to a pre-signed S3 URL in one request. Three Omni endpoints send the same recording in parts instead.

Use this route in place of steps 17 and 18 of the Video Selfie Flow. Use one route or the other, never both.

Select this route when one of these is true:

  • Your security policy does not permit a direct upload to Amazon S3.
  • The recording is large, and one request for the whole file is not reliable.

Info

The SDK does not use this route. In .standard mode, the SDK always reserves one URL and sends the recording in a single request. The endpoints below are an API-only route for Capture-Only mode.

Start the Upload

The response gives an uploadId. Every later call needs this value.

curl --location --request POST 'YOUR_API_URL/omni/video-selfie/upload/initiate' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN'

Send the Parts

The API gateway rejects a request larger than 10 MB. Divide the video into parts of about 7 MB. For example, split -b 7000000 archive.mp4 chunk_ writes the files chunk_aa, chunk_ab, and chunk_ac. Do not send parts near the 10 MB limit. Each request carries more than the part itself, so parts of 9 MB can still fail. 7 MB is the tested value.

Send each part in its own call. Increase partNumber by 1 for each part.

curl --location 'YOUR_API_URL/omni/video-selfie/upload/part?uploadId=UPLOAD_ID&partNumber=1' \
--header 'Content-Type: application/octet-stream' \
--header 'api-version: 1.0' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN' \
--data-binary '@/path/to/chunk_aa'

Each response gives an eTag. Keep each eTag with its part number.

Complete the Upload

This call tells the back end to put the parts together into one recording. Send the uploadId from the first call and the partNumber and eTag of each part.

curl --location 'YOUR_API_URL/omni/video-selfie/upload/complete' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN' \
--data '{
    "uploadId": "abc123",
    "parts": [
        { "partNumber": 1, "eTag": "bcae19" },
        { "partNumber": 2, "eTag": "d4f921" },
        { "partNumber": 3, "eTag": "a1b2c3" }
    ]
}'

Follow these rules for the upload in parts:

  • Read the uploadId from the response of the first call. Do not write it into the code.
  • Number the parts in sequence, from 1.
  • Send the parts in the same order as the divided files.
  • Put every part in the parts array. A missing part makes the assembly fail.

Flows Without Deepsight

Every flow on this page uses Deepsight. To run the same flow without it, make these two changes:

  1. In the SDK, set enabled: false and motion: false on DeepsightConfiguration, and set motion: false on addIdScan(...). Set the same values in the Dashboard flow.
  2. Omit Deepsight video. With enabled: false, the SDK records no video, so presignedVideoFileURL is nil. Omit POST /omni/recordings/import and omit the recordingId query parameter of omni/add/face/third-party.

Every other call stays, in the same order.


Additional APIs

These calls are optional. They read data from a session that is complete.

Get the Session Score

curl --location 'YOUR_API_URL/omni/get/score' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN'

Get the OCR Data of the ID

curl --location 'YOUR_API_URL/omni/get/ocr-data/v2' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN'

Get the Session Images

Base64 strings are large. Ask for the images that you need, in one call.

curl --location --request POST 'YOUR_API_URL/omni/get/images' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN' \
--data '{
    "images": [
        "fullFrameFrontID",
        "fullFrameBackID",
        "selfie",
        "signatureImageID",
        "ghostSignatureImageID",
        "ghostPortraitImageID"
    ]
}'

Get the Deepsight Video

curl --location 'YOUR_API_URL/omni/generateSessionRecordingDownloadUrl?interviewId=INTERVIEW_ID&type=selfie' \
--header 'Accept: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN'

Clean Up Local Data

Call IncdOnboardingManager.shared.deleteLocalUserData() after the flow exits. Call it from the success, error, and cancellation paths. See Capture-Only Mode.

Send or copy every capture you need before you make this call. On the error and cancellation paths, the uploads can be incomplete, and the call removes the files permanently.


For help, see API Reference or contact Incode support.