---
title: "Capture-Only End-to-End Integration"
url: "https://developer.incode.com/sdk-reference/android-capture-only-integration/"
section: "sdk-reference"
group: "Android SDK / Android Getting Started / Android Capture Only SDK"
version: "v1.1"
status: "live"
---
# Capture-Only End-to-End Integration

[Deepsight](/features-and-modules/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 Android 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](#basic-flow)**: An ID Capture module and a Selfie module. The user scans an ID and takes a selfie with Deepsight.
- **[Video Selfie Flow](#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](#flows-without-deepsight).

## Before You Start

You need these four items:

- An SDK integrated in `SdkMode.CAPTURE_ONLY`. See [Capture-Only Mode](/sdk-reference/android-capture-only-sdk/).
- Android SDK 5.54.0 or later. Earlier versions do not have the Deepsight configuration API. Versions 5.50.0 to 5.53.x have it, but `SelfieScanResult.presignedVideoFilePath` can reference a file that does not exist, which stops `omni/recordings/import`.
- An [API key](/get-started-with-incode/getting-credentials/) for your environment.
- A Dashboard flow (`configurationId`) with:
    - Deepsight turned on in the [Flow](/dashboard-platform-administration/flows-1/#flow-settings) or [Workflow](/dashboard-platform-administration/workflows-20/#workflow-settings) Settings tab.
    - The Deepsight settings of the [Face Capture](/dashboard-platform-administration/face-capture-dashboard/#configuration-options) module set to match the SDK `modality`.
    - Face Capture and [ID Capture](/dashboard-platform-administration/id-capture-dashboard/#configuration-options) modules with Motion set to match the SDK `motion`.


:::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 `WELCOME_API_URL` and `WELCOME_API_KEY`, the same names as the [installation](/sdk-reference/android-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 `WELCOME_API_URL` after `omni/start` needs these headers. An upload to a pre-signed URL does not use them.

| Header | Value |
| --- | --- |
| `x-api-key` | `WELCOME_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`, with two exceptions. Step 15 of the Video Selfie flow sends the audio file with `audio/mp4`. The part upload of the [alternative upload route](#alternative-upload-the-video-selfie-in-parts) sends each chunk with `application/octet-stream`. |

## SDK Configuration for Deepsight

Deepsight needs two changes in the SDK code, one for the ID scan and one for the selfie scan.

### ID Scan

`IdScan.Builder.setMotionEnabled(Boolean)` is available since Android SDK 5.50.0. 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](/dashboard-platform-administration/id-capture-dashboard/) module in Dashboard. If the two differ, the back end rejects `omni/add/front-id/v2` with `Invalid capture metadata`.


```kotlin
val idScan = IdScan.Builder()
    .setIdType(IdScan.IdType.ID)
    .setMotionEnabled(true)
    .build()
```

```java
IdScan idScan = new IdScan.Builder()
    .setIdType(IdScan.IdType.ID)
    .setMotionEnabled(true)
    .build();
```

### Selfie Scan

`SelfieScan.Builder.setDeepsightConfiguration(DeepsightConfiguration)` is available since Android SDK 5.50.0. It replaces `setVideoLivenessRecordingEnabled(Boolean)`, which is deprecated. See the [Migration Guide](/release-notes/migration-guide/#1-replace-setvideolivenessrecordingenabled-with-setdeepsightconfiguration).

The configuration has three independent fields:

| Field | Default | Description |
| --- | --- | --- |
| `setEnabled(Boolean)` | `true` | Turns Deepsight liveness on or off. When `false`, the SDK ignores the modality. |
| `setModality(Modality)` | `SINGLE_FRAME` | The capture mode. The SDK applies it only when `enabled` is `true`. |
| `setMotionEnabled(Boolean)` | `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 |
| --- | --- | --- |
| `SINGLE_FRAME` | No | No |
| `MULTIMODAL` | Yes | No |
| `VIDEO_LIVENESS` | Yes | Yes |

```kotlin
val selfieScan = SelfieScan.Builder()
    .setDeepsightConfiguration(
        DeepsightConfiguration.Builder()
            .setEnabled(true)
            .setModality(DeepsightConfiguration.Modality.VIDEO_LIVENESS)
            .setMotionEnabled(true)
            .build()
    )
    .build()
```

```java
SelfieScan selfieScan = new SelfieScan.Builder()
    .setDeepsightConfiguration(
        new DeepsightConfiguration.Builder()
            .setEnabled(true)
            .setModality(DeepsightConfiguration.Modality.VIDEO_LIVENESS)
            .setMotionEnabled(true)
            .build()
    )
    .build();
```

:::info

  The SDK applies `setDeepsightConfiguration()` and `IdScan.Builder.setMotionEnabled()` in `SdkMode.CAPTURE_ONLY` only. In other modes, the server configuration has priority and the SDK writes a warning to the log.
:::

### Result Fields

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

| Callback | Result | Field | Send it to |
| --- | --- | --- | --- |
| `onIdFrontCompleted()` | `IdScanResult` | `idImageBase64`, `metadata` | `omni/add/front-id/v2` |
| `onIdBackCompleted()` | `IdScanResult` | `idImageBase64`, `metadata` | `omni/add/back-id/v2` |
| `onSelfieScanCompleted()` | `SelfieScanResult` | `fullFrameSelfieBase64`, `metadata` | `omni/add/face/third-party` |
| `onSelfieScanCompleted()` | `SelfieScanResult` | `presignedVideoFilePath` | `omni/recordings/import` |
| `onVideoRecorded()` | `VideoSelfieResult` | `videoFilePath` | The URL from `omni/generateVideoSelfieUrl` |
| `onVideoRecorded()` | `VideoSelfieResult` | `idFrontImagePath` | `omni/videoselfie/compare-id/v2`, `omni/videoselfie/compare-ocr/v2` |
| `onVideoRecorded()` | `VideoSelfieResult` | `idBackImagePath` | `omni/videoselfie/compare-back-id/v2`, `omni/videoselfie/compare-back-ocr/v2` |

:::info
The SDK populates `metadata` in `SdkMode.CAPTURE_ONLY` only. In every other mode the field is `null`.

The SDK populates `videoFilePath` and `presignedVideoFilePath` on `SelfieScanResult` in `SdkMode.CAPTURE_ONLY`, and only when the modality is `VIDEO_LIVENESS`. In every other case both fields are `null`. Send `presignedVideoFilePath`, and keep `SelfieScanResult.videoFilePath` for local diagnosis only.
:::

Every asset field of `VideoSelfieResult` is a file path. The result has no base64 field. Read each file, then encode the image files as base64 for the JSON requests. Send the audio file and the video recording as binary.

## Basic Flow

Use this flow for Deepsight with an ID scan and a selfie scan.

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 scan, front side**: `POST /omni/add/front-id/v2?onlyFront=false`

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

:::warning

  The `onlyFront` query parameter of step 3 tells the back end whether a back image follows.

  - To scan both sides, use `IdScan.Builder.setScanStep(ScanStep.BOTH)`, send `onlyFront=false`, and make step 4.
  - To scan the front only, use `setScanStep(ScanStep.FRONT)`, send `onlyFront=true`, and omit step 4.

  `ScanStep.BOTH` is the default.
:::

:::info
Send the device fingerprint with `omni/add/device-fingerprint` directly after `omni/start`, before any capture. The SDK does the same in `SdkMode.STANDARD`: 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 | `FlowConfig.Builder.addID(...)` | `IdScanResult.idImageBase64` from `onIdFrontCompleted()` |
| 4 | the flow scans the back of a document | `addID(...)` with `IdScan.Builder.setScanStep(ScanStep.BOTH)`, the default | `IdScanResult.idImageBase64` from `onIdBackCompleted()` |
| 5 | the flow scans a document | `addID(...)` | none, this call carries no image |
| 6 | the selfie records a video for deepsight | `setDeepsightConfiguration(...)` with `Modality.VIDEO_LIVENESS` | `SelfieScanResult.presignedVideoFilePath` |
| 7, 8 | the flow takes a selfie | `FlowConfig.Builder.addSelfieScan(...)` | `SelfieScanResult.fullFrameSelfieBase64` |

If a module is not in the flow, its callback never fires and its result field is never set. If a module is in the flow and its field is `null`, the capture did not complete. Record the fault, as described for the [Video Selfie Flow](#video-selfie-flow).

### 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.

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

### Add the Device Fingerprint

Deepsight needs the device fingerprint. Read it from the SDK with `IncodeWelcome.getInstance().getDeviceFingerprint().toJson()`. This method returns the exact JSON body for the call. Send it without changes.

```kotlin
val fingerprintJson = IncodeWelcome.getInstance().getDeviceFingerprint().toJson()
```

```java
String fingerprintJson = IncodeWelcome.getInstance().getDeviceFingerprint().toJson();
```

```bash
curl --location 'WELCOME_API_URL/omni/add/device-fingerprint' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: WELCOME_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.

```json
{
    "sdkVersion": "5.50.0",
    "hash": "1234",
    "ip": "10.11.11.11",
    "osVersion": "31",
    "deviceModel": "MAO-LX9",
    "data": "{\"crossApp\":true,\"totalRam\":8018219008,\"availableRam\":4677599232}",
    "deviceType": "ANDROID",
    "algorithmName": "androidId",
    "algorithmVersion": "1.0.0"
}
```

### Add the Front of the ID

Send `idImageBase64` and `metadata` from the `IdScanResult` that `onIdFrontCompleted` returns.

```bash
curl --location 'WELCOME_API_URL/omni/add/front-id/v2?onlyFront=false' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: WELCOME_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN' \
--data '{
    "base64Image": "result.idImageBase64",
    "metadata": "result.metadata"
}'
```

### Add the Back of the ID

Send `idImageBase64` and `metadata` from the `IdScanResult` that `onIdBackCompleted` returns.

```bash
curl --location 'WELCOME_API_URL/omni/add/back-id/v2' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: WELCOME_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN' \
--data '{
    "base64Image": "result.idImageBase64",
    "metadata": "result.metadata"
}'
```

### Process the ID

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

### Send the Deepsight Video

`SelfieScanResult.presignedVideoFilePath` is a file path, 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 `presignedVideoFilePath` is `null`, there is no recording to send. Omit this step, and send the selfie without a `recordingId`.

With the `VIDEO_LIVENESS` modality this field must hold a path. A `null` value means the recording failed, and the session gets no video liveness check. Record the fault.

```bash
curl --location 'WELCOME_API_URL/omni/recordings/import' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: WELCOME_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.

### Send the Selfie Scan Image

Send `fullFrameSelfieBase64` and `metadata` from the `SelfieScanResult` that `onSelfieScanCompleted` returns. Add the `recordingId` from the previous step as a query parameter.

```bash
curl --location 'WELCOME_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: WELCOME_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN' \
--data '{
    "base64Image": "result.fullFrameSelfieBase64",
    "metadata": "result.metadata"
}'
```

### Process the Selfie Scan Image

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

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

### Finish the Session

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

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

## Video Selfie Flow

Use this flow for Deepsight with an ID scan, a selfie scan, and a Video Selfie.

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 scan, front side**: `POST /omni/add/front-id/v2?onlyFront=false` 
4. **ID scan, back side**: `POST /omni/add/back-id/v2` 
5. **ID scan**: `POST /omni/process/id` 
6. **Selfie Scan with Deepsight video**: `POST /omni/recordings/import` 
7. **Selfie Scan**: `POST /omni/add/face/third-party?imageType=selfie&recordingId=RECORDING_ID` 
8. **Selfie Scan**: `POST /omni/process/face?imageType=selfie` 
9. **Video Selfie, front document**: `POST /omni/videoselfie/compare-id/v2` 
10. **Video Selfie, front document**: `POST /omni/videoselfie/compare-ocr/v2` 
11. **Video Selfie, back document**: `POST /omni/videoselfie/compare-back-id/v2` 
12. **Video Selfie, back document**: `POST /omni/videoselfie/compare-back-ocr/v2` 
13. **Video Selfie, image**: `POST /omni/add/face/third-party?imageType=videoSelfie` 
14. **Video Selfie, image**: `POST /omni/process/face?imageType=videoSelfie` 
15. **Video Selfie, speech check**: `POST /omni/add/speech?audioOnly=true` 
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](#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 `setVoiceConsentEnabled(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 to 8 and step 19 are the same as the [Basic Flow](#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 carry no tag because the Video Selfie always records.

| Steps | Make them when | Builder option | Default | Result field |
| --- | --- | --- | --- | --- |
| 9, 10 | the Video Selfie scans the front of the document | `setIdScanEnabled(true)` | on | `idFrontImagePath` |
| 11, 12 | the Video Selfie scans the back of the document | `setIdScanEnabled(true)` | on | `idBackImagePath` |
| 13, 14 | the Video Selfie takes a selfie | `setSelfieScanEnabled(true)` | on | `selfieImagePath` |
| 15 | the recording has audio **and** you want the back end to check what the user said | `setVoiceConsentEnabled(true)` or `setRandomQuestionsEnabled(true)` | off | `audioFilePath` |
| 16 | the consent has a face frame **and** you want the back end to match that face | `setVoiceConsentFaceRecognitionEnabled(true)`, `setVoiceConsentEnabled(true)`, and `setSelfieScanEnabled(true)`. All three are necessary. | off | `voiceConsentSelfiePath` |

`setDisableAudio(true)` stops the recording of audio. The default is `false`. With audio off, `audioFilePath` and `voiceConsentSelfiePath` are `null` and you omit steps 15 and 16.

The four comparison options (`setFrontIdComparisonEnabled`, `setFrontIdOcrComparisonEnabled`, `setBackIdComparisonEnabled`, and `setBackIdOcrComparisonEnabled`) have no effect in `SdkMode.CAPTURE_ONLY`. The SDK makes no comparison calls in this mode. Your back end decides which comparisons to make when it makes or omits steps 9 to 12.

:::info
A `null` 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 `null` and you omit its calls. That is the normal case.

  If you did enable a step and its field is `null`, 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.
:::



### Get the Questions and the Consent Phrase

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 `setRandomQuestions(...)` and the voice consent options, 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 `SdkMode.CAPTURE_ONLY` the SDK does not make this call. It shows only the text you give it. In `SdkMode.STANDARD` the SDK makes the call itself at the start of the step and shows what it returns.

```bash
curl --location 'WELCOME_API_URL/omni/get/questionAndAnswer?numberOfQuestions=1&returnVoiceConsentQuestion=true' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: WELCOME_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 to 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. Read the file at `VideoSelfieResult.idFrontImagePath` or `VideoSelfieResult.idBackImagePath`. Then encode the bytes as base64 with no line breaks.

| Step | Endpoint | Image |
| --- | --- | --- |
| 9 | `omni/videoselfie/compare-id/v2` | `idFrontImagePath` |
| 10 | `omni/videoselfie/compare-ocr/v2` | `idFrontImagePath` |
| 11 | `omni/videoselfie/compare-back-id/v2` | `idBackImagePath` |
| 12 | `omni/videoselfie/compare-back-ocr/v2` | `idBackImagePath` |

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

- If `idFrontImagePath` is set, make steps 9 and 10.
- If `idBackImagePath` is set, make steps 11 and 12.
- If a field is `null`, omit the two steps for that side. If the Video Selfie scanned that side, a `null` value means the capture failed.

This is the same rule the SDK applies in the 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.

`VideoSelfie.Builder.setIdScanEnabled(true)` gives the Video Selfie a front step and a back step, so a two-sided document sets both fields and all four steps apply. In `SdkMode.CAPTURE_ONLY` the comparison options do not remove any of these steps. To omit a comparison, do not make its call.

:::info
The result gives paths, not images. `VideoSelfieResult` has no base64 fields. Your app must read each file and encode it.
:::

### Step 9: Compare the ID

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

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

Response (`200`):

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

Response (`400`):

```json
{
    "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 | Meaning |
| --- | --- |
| `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.

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

Response (`200`):

```json
{
    "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 the file at `VideoSelfieResult.idBackImagePath`.

```bash
curl --location 'WELCOME_API_URL/omni/videoselfie/compare-back-id/v2' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: WELCOME_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN' \
--data '{
    "base64Image": "BASE64_OF_ID_BACK_IMAGE_PATH"
}'
```

```bash
curl --location 'WELCOME_API_URL/omni/videoselfie/compare-back-ocr/v2' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: WELCOME_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN' \
--data '{
    "base64Image": "BASE64_OF_ID_BACK_IMAGE_PATH"
}'
```

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 the Video Selfie takes a selfie, so when `selfieImagePath` is set.

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 Scan 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 Scan | `SelfieScanResult.fullFrameSelfieBase64` | `selfie` |
| 13, 14 | Video Selfie | `VideoSelfieResult.selfieImagePath` | `videoSelfie` |

:::warning
`FACE_MATCH` changes the `imageType`. The value of `imageType` in steps 13 and 14 follows `VideoSelfie.Builder.setSelfieMode(...)`:

  - `SelfieMode.SELFIE_MATCH`, the default, sends `imageType=videoSelfie`. The back end compares the Video Selfie image with the earlier selfie.
  - `SelfieMode.FACE_MATCH` sends `imageType=selfie`. The Video Selfie image becomes the selfie of the session, and the back end compares it with the ID.

  The examples below show the default.
:::

```bash
curl --location 'WELCOME_API_URL/omni/add/face/third-party?imageType=videoSelfie' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: WELCOME_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN' \
--data '{
    "base64Image": "BASE64_OF_SELFIE_IMAGE_PATH"
}'
```

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

```bash
curl --location 'WELCOME_API_URL/omni/process/face?imageType=videoSelfie' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: WELCOME_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN'
```

Response (`200`):

```json
{
    "confidence": 1
}
```

### Step 15: Send the Voice Consent Audio

This call is optional. Make it to check the words the user said. It needs `audioFilePath` 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](#get-the-questions-and-the-consent-phrase).

Step 15 sends the recording at `VideoSelfieResult.audioFilePath`. The body is the audio file itself, not JSON, so this call uses `Content-Type: audio/mp4`. The speech recognition runs inside this call. There is no process step after it.

```bash
curl --location 'WELCOME_API_URL/omni/add/speech?audioOnly=true' \
--header 'Content-Type: audio/mp4' \
--header 'api-version: 1.0' \
--header 'x-api-key: WELCOME_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN' \
--data-binary '@/path/to/audioFilePath.mp4'
```

:::warning
The iOS 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. If you write one back end for both platforms, make it accept the shape each platform sends.
:::

### Step 16: Send the Voice Consent Face

Optional. Make this call to match the face of the user during the consent. It needs `voiceConsentSelfiePath` to be set.

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

Send the frame at `VideoSelfieResult.voiceConsentSelfiePath` 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 `CommonConfig.Builder.setRecognitionThreshold(...)`. The default is `0.6`.

```bash
curl --location 'WELCOME_API_URL/omni/videoselfie/voiceconsent/add/face' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: WELCOME_API_KEY' \
--header 'X-Incode-Hardware-Id: SESSION_TOKEN' \
--data '{
    "base64Image": "BASE64_OF_VOICE_CONSENT_SELFIE_PATH",
    "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.

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

The response gives the URL for the recording:

```json
{
    "url": "PRESIGNED_UPLOAD_URL"
}
```

### Step 18: Send the Recording

Send the file at `VideoSelfieResult.videoFilePath` to the `url` from step 17.

```bash
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).

## Alternative: Upload the Video Selfie in Parts

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

Use it in place of steps 17 and 18 of the [Video Selfie Flow](#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 the `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.

```bash
curl --location --request POST 'WELCOME_API_URL/omni/video-selfie/upload/initiate' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: WELCOME_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, which keeps each request below that limit. 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.

```bash
curl --location 'WELCOME_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: WELCOME_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.

```bash
curl --location 'WELCOME_API_URL/omni/video-selfie/upload/complete' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: WELCOME_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" }
    ]
}'
```

Obey these four 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 `setEnabled(false)` on the `DeepsightConfiguration`, and set `setMotionEnabled(false)` on both the `DeepsightConfiguration` and the `IdScan.Builder`. Set the same values in the Dashboard flow.
2. Omit the Deepsight video. With `setEnabled(false)` the SDK records no video, so `presignedVideoFilePath` is `null`. 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

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

### Get the OCR Data of the ID

```bash
curl --location 'WELCOME_API_URL/omni/get/ocr-data' \
--header 'Content-Type: application/json' \
--header 'api-version: 1.0' \
--header 'x-api-key: WELCOME_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.

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

### Get the Deepsight Video

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

## Clean Up the Local Data

Call `IncodeWelcome.deleteUserLocalData(context)` after the flow exits. Call it from the success, error, and cancellation callbacks. See [Capture-Only Mode](/sdk-reference/android-capture-only-sdk/).

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 additional help, see the [Android API Reference](/sdk-reference/android-api-reference/) or contact Incode support.