> For the complete documentation index, see [llms.txt](https://doc.ozforensics.com/oz-knowledge/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://doc.ozforensics.com/oz-knowledge/general/integration-quick-start-guides/how-to-integrate-oz-liveness-using-the-non-persistent-oz-api-mode.md).

# How to Integrate Oz Liveness Using the Non-Persistent Oz API Mode

This guide walks you through the full integration: getting a session token, capturing media with the SDK, sending the resulting `OzCapsula` container to Oz API Instant, reading the response, making the accept/reject decision, reducing the response size, and running minimal diagnostics.

This flow combines three things:

* **Capture mode** – the Web SDK (or Mobile SDK) captures the Liveness video in the browser/app and the Client backend forwards it to Oz API. Capture results are not sent from the SDK straight to Oz API; the Client backend acts as an intermediary.
* **OzCapsula** – a [proprietary encrypted binary container](/oz-knowledge/general/readme/proprietary-format-ozcapsula-data-container.md). The SDK packages captured media and metadata into it; Oz API decrypts and processes it. It protects the captured data from tampering between the user's device and Oz API. The container is an **opaque encrypted blob**. You **cannot** decode it, inspect it, or extract individual images / frames / metadata from it on the device. It must be forwarded as-is to your backend. If you need access to captured images (e.g., the best shot), retrieve them from the Oz API analysis response on the server side.
* **Instant API** – the non-persistent mode of Oz API. Results are returned immediately in the response and nothing is stored.

Together: the SDK captures and packages media into OzCapsula → your backend forwards the container to Oz API Instant → Oz API returns the result synchronously, storing nothing.

<figure><img src="/files/XwykQLGIuk4698LjxPiw" alt=""><figcaption></figcaption></figure>

## Glossary

A few terms used throughout this guide:

* `{{host}}` – the base URL of your Oz API deployment. Provided to you during deployment setup.
* **Session token** – a short-lived token that ties an OzCapsula to a specific capture session, preventing replay of the same container later. Mandatory for OzCapsula.
* **Folder** – the response object Oz API creates for a single submission. It groups the submitted media and the analyses that were applied to them.
* **Analysis type** – what Oz API runs on the media. The API returns `QUALITY` for **Liveness** analyses and `BIOMETRY` for **Face Matching** analyses.

## Component versions

| Component                  | Minimum version |
| -------------------------- | --------------- |
| Oz API                     | 6.6.1           |
| Mobile SDKs (iOS, Android) | 10.0.0          |
| Web SDK                    | 1.9.10          |

{% stepper %}
{% step %}

## Get a session token

OzCapsula requires a session token for each capture session.

To enable the session token functionality, generate a pair of JWT keys. Please find the instructions on how to create them in the [API Instant helm chart](/oz-knowledge/guides/administrator-guide/installation-in-kubernetes/helm-charts.md#instant-api): proceed to `readme.md` and, in the **Preparation** section, locate sub-section **Create a JWT secret**. Once you have keys, create a folder within your installation, place keys there, and put this path into `API_KEYS_DIR` (e.g., `API_KEYS_DIR=/opt/oz/api/keys`). This should be done once for the installation.

Request a session token from Oz API Instant. Request it before each capture session, as close to the moment the user starts capture as possible.

**Endpoint:**

```
GET {{host}}/api/authorize/session_token
```

**Headers:**

* `Content-Type: application/json`

**Request:**

```shell
curl -L 'https://{{host}}/api/authorize/session_token' \
  -H 'Content-Type: application/json'
```

**Response:**

```json
{
  "session_token": "<session_token>"
}
```

The token is short-lived (default lifetime: 900 seconds). Request one per capture session and pass it to the SDK immediately – do not cache or reuse tokens across sessions.

Authorization mechanism for the call to Oz API depends on installation, by default, no access token is required in the headers. If it is required in your case, please check [Authentication](/oz-knowledge/guides/developer-guide/api/oz-api/use-cases/authentication.md).
{% endstep %}

{% step %}

## Capture and package on the device

The SDK captures media from the user and packages it into an `OzCapsula` container.

### Mobile SDK (iOS, Android)

Launch analysis with the session token. The container is returned through the result callback.

#### Android (Kotlin)

```kotlin
// Launch the capture screen
val captureRequest = CaptureRequest(
    listOf(
        AnalysisProfile(
            Analysis.Type.QUALITY,
            listOf(MediaRequest.ActionMedia(OzAction.Blank))
        )
    )
)
val intent = OzLivenessSDK.createMediaCaptureScreen(captureRequest, sessionToken)
startActivityForResult(intent, REQUEST_LIVENESS_CONTAINER)

// Obtain the result
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (requestCode == REQUEST_LIVENESS_CONTAINER) {
        when (resultCode) {
            OzLivenessResultCode.SUCCESS -> {
                val container = OzLivenessSDK.getContainerFromIntent(data)
                handleContainer(container)
            }
            OzLivenessResultCode.USER_CLOSED_LIVENESS -> { /* user closed the screen */ }
            else -> {
                val errorMessage = OzLivenessSDK.getErrorFromIntent(data)
                /* show error */
            }
        }
    }
}
```

#### iOS (Swift)

```swift
// Launch the capture screen
let mediaRequest = MediaRequest.action(.selfie)

let profile = AnalysisProfile(
    mediaList: [mediaRequest],
    type: .quality,
    params: [:]
)

let request = CaptureRequest(
    analysisProfileList: [profile],
    cameraPosition: .front
)

let livenessVC = try OZSDK.createMediaCaptureScreen(
    delegate,
    request,
    sessionToken: sessionToken
)

livenessVC.modalPresentationStyle = .fullScreen
parent.present(livenessVC, animated: true)

// Obtain the result
extension YourViewController: LivenessDelegate {
    func onResult(container: DataContainer) {
        // Pass container to Step 5
        handleContainer(container)
    }

    func onError(status: OZVerificationStatus?) {
        // user cancelled, capture failed, etc.
    }
}
```

The container is returned as a `bytearray` (Android) or `DataContainer` (iOS). Forward it to your backend without any transformation.

### Web SDK

Call `OzLiveness.Open()` with the session token. When capture completes, the SDK fires the `on_capture_complete` callback. The container is delivered as the second argument.

```javascript
OzLiveness.open({
  lang: 'en',
  session_token: session_token,
  action: ['video_selfie_blank'], // request passive liveness video
  on_capture_complete: function(action, ozCapsula) {
    // ozCapsula is a Blob (application/octet-stream)
    // forward it to your backend
  },
  on_error: function (err) { /* handle error */ }
});
```

The container arrives as a `Blob` with MIME type `application/octet-stream`. Forward it to your backend without any transformation.
{% endstep %}

{% step %}

## Liveness analysis: send the container to Instant API

Your backend submits the container in a single synchronous `POST` request.

**Endpoint:**

```
POST {{host}}/api/instant/folders/
```

**Headers:**

* `Content-Type: application/octet-stream` (mandatory)

**Request body:** the raw container bytes – no JSON envelope, no multipart, no base64 wrapping.

```shell
curl -X POST '{{host}}/api/instant/folders/' \
  -H 'Content-Type: application/octet-stream' \
  --data-binary '@/path/to/container.dat'
```

**On success:** HTTP `201` with a JSON folder object describing the result.

**On container-level error:** HTTP `400` (see Non-success outcomes).

The `QUALITY` (liveness) analysis provides the best shot. Locate the `QUALITY` entry in the `analyses[]` array and read:

```json
$.analyses[*].results_media[*].output_images[*].image_b64
```

The value is a base64-encoded JPEG. Decode it to JPEG.
{% endstep %}

{% step %}

## Biometry (Face Matching) analysis (optional)

If you don't need face matching, skip this step and go straight to **Step 5**.

Call `POST /api/instant/folders/` with your reference photo and the best shot you've obtained from the Liveness analysis at **Step 3**.

{% hint style="info" %}
**Your media file keys and the corresponding filenames must match.**
{% endhint %}

Request body:

```json
{
  "media:tags": {
    "UUID_of_reference_photo.jpeg": ["photo_selfie"],
    "best_shot.jpeg": ["photo_selfie"]
  },
  "analyses": [
    { "type": "biometry" }
  ]
}
```

The response will contain the result of the biometry analysis.
{% endstep %}

{% step %}

## Read the response

#### Top-level fields

| Field                | Use                                                                                                                                                               |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `folder_id`          | Unique identifier of the request                                                                                                                                  |
| `resolution_status`  | Whether processing completed: `FINISHED` or `FAILED`                                                                                                              |
| `system_resolution`  | Overall result: `SUCCESS`, `DECLINED`, or `FAILED`                                                                                                                |
| `resolution_comment` | Note about the analysis resolution. Useful on `FAILED` (lists which analyses failed)                                                                              |
| `meta_data`          | Metadata block. The block contains `event_session_id`, the session identifier which is required for tracing the request back to the user session in your frontend |
| `media`              | Media files submitted in the container                                                                                                                            |
| `analyses`           | Array of per-analysis results                                                                                                                                     |

#### Inside `analyses[]`

| Field                         | Use                                                                                                             |
| ----------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `type`                        | `QUALITY` for liveness, `BIOMETRY` for face matching                                                            |
| `state`                       | Processing state of this analysis: `FINISHED` or `FAILED`                                                       |
| `resolution_status`           | Outcome of this analysis: `SUCCESS`, `DECLINED`, or `FAILED`                                                    |
| `error_code`, `error_message` | Shown only when the analysis failed technically                                                                 |
| `results_data`                | Aggregated result values                                                                                        |
| `results_media`               | Generated media items. For the `QUALITY` analysis, contains the best shot in `output_images[]` – see **Step 3** |
| `source_media`                | Input media                                                                                                     |

### Make the accept/reject decision

Use only these two top-level fields:

```json
$.resolution_status   // "FINISHED" – analyses completed; "FAILED" – error during analysis processing
$.system_resolution   // "SUCCESS", "DECLINED", or "FAILED"
```

Interpret `system_resolution`:

* `SUCCESS` – all checks passed; accept the user.
* `DECLINED` – one or more checks failed; reject the user.
* `FAILED` – a system error prevented at least one analysis from completing; do not treat as accept or reject (see Non-success outcomes).

For per-analysis verdicts (for example, to know whether liveness passed but biometry failed), read `$.analyses[*].resolution_status` with the same three values.

### Other fields

The response also contains internal identifiers (`group_id`, `media_association_id`), file hashes, operator-related fields (`operator_comment`, `operator_status`, `is_cleared`), and other system fields. They are not needed for the integration flow described in this guide.
{% endstep %}

{% step %}

## Non-success outcomes

### Invalid container – HTTP `400`

When the container fails validation, Oz API responds with HTTP `400` and `error_message: "Invalid request data"`. The specific `error_code` indicates the cause:

| `error_code` | Condition                                                                                           |
| ------------ | --------------------------------------------------------------------------------------------------- |
| `3`          | Missing payload                                                                                     |
| `4`          | No media in the container                                                                           |
| `5`          | Container is damaged                                                                                |
| `13`         | No container in the request                                                                         |
| `14`         | Invalid container – any reason (decryption, signature, hash, or `session_token` validation failure) |

Codes `13` and `14` fire before the container is unpackaged; codes `3`, `4`, `5` fire after unpackaging when the payload itself fails business-level validation.

For code `14`, send the container instance and the `error_code` to Oz Forensics support – the specific cause is only available in server-side logs.

### Declined – HTTP `201`

The container was valid and the analyses ran, but at least one check did not pass.

* `resolution_status: FINISHED`.
* `system_resolution: DECLINED`.
* `resolution_comment: "[]"` (not informative – the decline reason is implicit in the per-analysis result).
* Per-analysis `resolution_status: DECLINED`.

### Failed – HTTP `201`

A system error prevented at least one analysis from completing.

* `resolution_status: FAILED`.
* `system_resolution: FAILED`.
* `resolution_comment` – string listing which analyses failed and with what code.
* Per-analysis `state: FAILED`, `resolution_status: FAILED`, `error_code` and `error_message` populated.
  {% endstep %}
  {% endstepper %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://doc.ozforensics.com/oz-knowledge/general/integration-quick-start-guides/how-to-integrate-oz-liveness-using-the-non-persistent-oz-api-mode.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
