# AutoCapture

> **📢 Upgrading from older versions?** See the [Migration Guides](docs/README.md#migration-guides) for breaking changes.

This project contains Datachecker's AutoCapture tool, that captures images of paper documents + identity documents (ID/Passport/Driver license). The tool only takes a capture once a document is detected and it passes the quality control.

The tool will be run in the browser and is therefore written in JavaScript.

## Trigger mechanism

The tool performs the following checks:

- Is the environment not too dark (under exposure)?
- Is there a document?
- Is the detected document not too far?
- Is the detected document not too close?
- Is the image sharp?

## Prerequisites

Please visit [Datachecker API documentation](https://developer.datachecker.nl/).

- Datachecker [OAuth Token](#oauth-token)
- Datachecker [SDK Token](#sdk-token)

## Compatibility

The SDK requires a browser that supports at least ECMAScript 12 (ES12). It is highly recommended to use the latest version of your preferred browser to ensure compatibility and access to the latest features and security updates.

Using the latest browser versions will ensure that all modern JavaScript features required by the SDK are supported.

### Minimum requirements

The SDK runs its image processing, document detection, glare classification and face detection in WebAssembly, and requires **WebAssembly SIMD**: the bundled OpenCV build is compiled for it, and the ONNX Runtime the detection models run on ships SIMD-only. There is no fallback for a device that lacks either: the SDK refuses to start rather than degrading.

| Requirement     | Minimum version                                                                                |
| --------------- | ---------------------------------------------------------------------------------------------- |
| Android         | Chrome 91 + / Samsung Internet 16 + (the **browser** version matters, not the Android version) |
| Chrome / Edge   | 91                                                                                             |
| Firefox         | 89                                                                                             |
| iOS / iPadOS    | **16.4** (all iOS browsers use WebKit, so this applies regardless of browser)                  |
| Safari on macOS | 16.4                                                                                           |

Devices below these versions are refused with a `device_error` before the camera opens. See [Unsupported devices](#unsupported-devices).

**iOS and Android fail differently, and the message you show should differ too.**

- **On iOS, the browser is the operating system.** Every iOS browser uses WebKit, so WebAssembly SIMD arrives with iOS 16.4 and cannot be obtained any sooner. Installing a different browser changes nothing. For hardware that cannot go past iOS 15 (iPhone 7 and older, still receiving 15.8.x security updates) this is permanent: no update will ever make the SDK work. "Update iOS, or use a different device" is the only honest message.
- **On Android, the browser updates independently of the operating system.** Chrome and the Android System WebView update through the Play Store, so a phone on an old Android release usually still runs a current browser with SIMD. A 2016 handset that keeps receiving Chrome updates works fine. When Android does fail it is normally the *browser* that is stale, not the OS: most often on devices without Play Store access (some Huawei models, AOSP or regional builds), or an in-app browser sitting on an outdated System WebView. Here "update your browser, or open the link in Chrome" is usually actionable.
- **WebAssembly can be switched off on an otherwise-current device.** iOS Lockdown Mode disables it, as do some managed-browser policies. These report `7002` rather than `7003`, so you can tell them apart from old hardware. Unlike old hardware, the user can often fix it.

### Unsupported devices

A device that cannot run the SDK is refused on the `onError` callback **before** the camera opens. Both codes share the `device_error` category, so the simplest integration branches on the prefix; the individual codes let you tailor the message.

| Code                | Meaning                              | What to tell the user                                                                                                        |
| ------------------- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
| `device_error:7002` | No WebAssembly at all                | Usually a restricted browser mode. On iOS, check whether Lockdown Mode is on                                                 |
| `device_error:7003` | WebAssembly present but without SIMD | On iOS: update to 16.4 or later, otherwise the device is too old. On Android: update the browser, or open the link in Chrome |

**Do not offer a retry for either.** Nothing about the device changes between attempts. Recovery, where it exists, needs an action outside the page: an OS update, a browser update, or turning off Lockdown Mode.

`7002` is the one case that may be user-fixable on current hardware, since Lockdown Mode and browser policies can be turned off. That is why it is reported separately rather than folded into `7003`.

These codes match the FaceVerify SDK, so an integration embedding both can share one handler.

## Steps

1. Request [OAUTH Token](#oauth-token)
2. Put OAuth in header
3. SDK [configuration](#configuration) (add SDK token)
4. Run SDK

## OAuth Token

Datachecker uses OAuth authorization. In order to request the [SDK token](#sdk-token) you will need to provide a valid OAuth token in the header.

Example header:

```javascript
header = {'Authorization': `Bearer ${response.accessToken}`}
```

This OAuth token can be retrieved with the [Datachecker OAuth Token API](https://developer.datachecker.nl/?urls.primaryName=v2#/ProductApi/ProductApi_OAuthToken). The scope `"productapi.sdk.read"` needs to be present to make use of the [SDK token](#sdk-token). If this scope is missing you will not be able to retrieve an SDK token.

Example OAuth:

```javascript
fetch(<BASE_ENDPOINT>+"/oauth/token", {
    method: 'POST',
    body: JSON.stringify({
        "clientId": <CLIENTID>,
        "clientSecret": <CLIENTSECRET>,
        "scopes": [
            "productapi.sdk.read",
        ]
    })
})
.then(response => response.json())
```

Note: Contact Datachecker for *client_id* and *client_secret*.

## SDK Token

The SDK is locked. In order to use the SDK in production a *token* is required. The application can only be started with a valid token. This token is a `base64` string. The token can be generated by calling the [Datachecker SDK Token API](https://developer.datachecker.nl/?urls.primaryName=v2#/ProductApi/ProductApi_SdkToken).

Example:

```javascript
fetch(<BASE_ENDPOINT>+"/sdk/token?customer_reference=<CUSTOMER>&services=AUTO_CAPTURE", {
    method: 'GET',
    headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
        'Authorization': `Bearer <ACCESSTOKEN>`
    }
})
.then(response => response.json())
```

## Configuration

To run this tool, you will need initialise with the following variables.

| **ATTRIBUTE**       | **FORMAT**              | **DEFAULT VALUE**                             | **EXAMPLE**                                 | **NOTES**                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| ------------------- | ----------------------- | --------------------------------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ALLOWED_DOCUMENTS` | object                  | see [ALLOWED DOCUMENTS](#allowed-documents)   | see [ALLOWED DOCUMENTS](#allowed-documents) | **optional**<br> Enable or disable flipping of certain documents.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `APPROVAL`          | bool                    | `false`                                       | `false`                                     | **optional**<br> Approval screen after capture as an extra quality check.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `ASSETS_FOLDER`     | string                  | `""`                                          | `"../"`                                     | **optional**<br> Specifies location of **locally hosted** assets folder. (see [Asset Fetching Configuration](#asset-fetching-configuration))                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `ASSETS_MODE`       | string                  | `"CDN"`                                       | `"LOCAL"`                                   | **optional**<br> Specifies mode of asset fetching, either through CDN or locally hosted assets. (see [Asset Fetching Configuration](#asset-fetching-configuration))                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `BACKGROUND_COLOR`  | string (Hex color code) | `"#1d3461"`                                   | `"#1d3461"`                                 | **optional**<br> Specifies the background color using a hex color code.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `CAPTURE_BTN_AFTER` | int                     | `10000` (AutoCapture) / `5000` (PaperCapture) | `0`                                         | **optional**<br> Configures a delay (in milliseconds) after which an instructional dialog and manual capture button appear. Setting to `0` disables the feature. When enabled, users can manually trigger capture at their convenience while auto-capture remains active. The dialog appears once per session. The default ensures a person is never left unable to continue on a document the SDK does not auto-capture (see [Allowed documents](#allowed-documents)).                                                                                                                                                                                                                                                                                                      |
| `CONTAINER_ID`      | string                  |                                               | `"AC_mount"`                                | **required**<br> _div id_ to mount tool on. If the `div` does not exist it will be created and placed in `<body>`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `DEBUG`             | bool                    | `false`                                       | `false`                                     | **optional**<br> When debug is `true` more detailed logs will be visible.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `DESKTOP_MODE`      | bool                    | `false`                                       | `false`                                     | **optional**<br> Enables all cameras for testing/development purposes. **FOR TESTING ONLY - DO NOT USE IN PRODUCTION.** Desktop cameras are often labeled with `facingMode: 'user'` instead of `'environment'`, which would normally be filtered out. This mode bypasses camera filtering to allow testing on desktop devices, including virtual cameras. Production environments should always use `false` to ensure only back-facing cameras (environment) are available, preventing accidental use of front-facing cameras on mobile devices.                                                                                                                                                                                                                             |
| `LANGUAGE`          | string                  | `"nl"`                                        | `"nl"`                                      | **required**<br> Notifications in specific language.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `LOGO`              | string / null           | (embedded DataChecker logo)                   | `"https://example.com/logo.png"`            | **optional**<br> White-label the footer logo. Provide an `https:` image URL, a relative path to a self-hosted image (e.g. `"assets/logo.png"`, resolved against your page), or an inline `data:image/...` URI, to show your own logo instead of the default DataChecker logo. Any web image format is accepted (PNG, JPG, WEBP, SVG, ...); the logo is rendered as an `<img>`, so an SVG file, URL, or `data:image/svg+xml` URI works like any other. Set to `null` to hide the footer logo entirely. Leave unset to keep the default. Unsafe values (`http:`, `javascript:`, protocol-relative `//host`, non-image `data:`) are ignored and the default is used. The logo is scaled to fit the footer, so a wide logo (similar to the default's proportions) displays best. |
| `onComplete`        | javascript function     |                                               | `function(data) {console.log(data)}`        | **required**<br> Callback function on _complete_.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `onError`           | javascript function     | `function(error) {console.log(error.code)}`   | `function(error) {console.log(error.code)}` | **required**<br> Callback that fires when an error occurs. Receives `{ code, message, stack, chain }`. See [Error Codes](#error-codes).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `onUserExit`        | javascript function     | `function(error) {console.log(error)}`        | `function(error) {window.history.back()}`   | **required**<br> Callback function on _user exit_.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `ROI_MODE`          | string                  | `"landscape-landscape"`                       | `portrait-landscape`                        | **optional**<br> Frame orientation options: `"portrait-landscape"`, `"landscape-landscape"`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `SDK_MODE`          | string                  | `"autocapture"`                               | `"papercapture"`                            | **optional**<br> Specifies mode of the SDK, supported modes are: `"autocapture"`, `"papercapture"` (see [SDK Modes](#sdk-modes))                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `TOKEN`             | string                  |                                               | see [SDK Token](#sdk-token)                 | **required**<br> Datachecker SDK token.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |

## SDK Modes

The SDK offers two operational modes:

- **AutoCapture**: Optimized for real-time capture of identity documents such as IDs, passports, and driver licenses. A capture is triggered only when a document is detected and passes all quality checks.
- **PaperCapture**: Tailored for capturing paper documents (e.g., work permits). Like AutoCapture, it ensures a capture occurs only after document detection and successful quality validation.

Refer to [Configuration](#configuration) for details on how to set the desired mode.

### PaperCapture Configuration

```javascript
let AC = new AutoCapture();
AC.init({
    CONTAINER_ID: ...,
    LANGUAGE: ...,
    TOKEN: ...,
    SDK_MODE: "papercapture",
    onComplete: ...,
    onError: ...,
    onUserExit: ...
});
```

## Asset fetching Configuration

AutoCapture requires fetching assets, which can be done either through a CDN or by hosting them locally. Configure this in the tool settings as follows:

### CDN Configuration

```javascript
// configuration
{
    ASSETS_MODE: "CDN",
    // other configurations
}
```

### Locally Hosting Configuration

To host assets locally, first copy them to your desired location:

```bash
cp -r dist/assets/ path/to/hosted/assets/
```

Then, configure the tool to use these local assets:

```javascript
// configuration
{
    ASSETS_MODE: "LOCAL",
    ASSETS_FOLDER: "path/to/hosted/assets/",
    // other configurations
}
```

For comphrehensive integration examples, please refer to our [Integration Examples](examples/README.md).

### Version Control

To ensure compatibility:

- **Separate Asset Versioning**: The assets directory contains a version file, separate from the main file's version.
- **Compatibility Check**: The main file will perform a version check and throw an error if the versions are incompatible.

## Content Security Policy (CSP)

AutoCapture is designed to work with Content Security Policy (CSP) enabled. The SDK requires specific CSP directives to load assets (e.g., scripts, models, images) and execute WebAssembly for document detection.

### Required CSP Directives

Ensure your CSP policy includes the following directives. Adjust domains based on your environment (e.g., use `https://developer.datachecker.nl` for Datachecker production api).

- `default-src 'self';`
- `script-src 'self' https://cdn.jsdelivr.net 'wasm-unsafe-eval' 'unsafe-inline' blob:;`
- `style-src 'self' 'unsafe-inline';`
- `connect-src 'self' https://developer.datachecker.nl https://cdn.jsdelivr.net data:;`
- `img-src 'self' data: blob: https://cdn.jsdelivr.net;`
- `worker-src 'self' blob:;`
- `object-src 'self' blob:;`
- `frame-src 'self' blob:;`
- `base-uri 'none';`

## Handling callbacks

Within the application, you can take advantage of four callback functions to enhance the user experience and manage the flow of your process.

Note: When integrating the application into Native Apps using web views, it's essential to adapt and utilize these callback functions according to the conventions and requirements of the native platforms (e.g., iOS, Android). Native app development environments may have specific ways of handling JavaScript callbacks, and you should ensure seamless communication between the web view and the native code.

Example Web (JS):

```javascript
let AC = new AutoCapture();
AC.init({
    CONTAINER_ID: 'AC_mount',
    LANGUAGE: 'en',
    TOKEN: "<SDK_TOKEN>",
    onComplete: function(data) {
        console.log(data);
    },
    onError: function(error) {
        console.error(error.code, error.stack)
    },
    onUserExit: function(error) {
        console.log(error);
        window.history.back();
    }
});
```

| **ATTRIBUTE** | **FORMAT**          | **DEFAULT VALUE**                           | **EXAMPLE**                                 | **NOTES**                                                                                                                               |
| ------------- | ------------------- | ------------------------------------------- | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `onComplete`  | javascript function |                                             | `function(data) {console.log(data)}`        | **required**<br> Callback that fires when all interactive tasks in the workflow have been completed.                                    |
| `onError`     | javascript function | `function(error) {console.log(error.code)}` | `function(error) {console.log(error.code)}` | **required**<br> Callback that fires when an error occurs. Receives `{ code, message, stack, chain }`. See [Error Codes](#error-codes). |
| `onUserExit`  | javascript function | `function(error) {console.log(error)}`      | `function(error) {window.history.back()}`   | **required**<br> Callback that fires when the user exits the flow without completing it.                                                |

### onComplete

This callback function will be called once all the tasks within the workflow succesfully have been completed. This callback function is **required**. The `data` parameter within the function represents the [output](#output) of the completed process. You can customize this function to handle and display the data as needed.

Example Web (JS):

Within the example below we are logging the output (`data`) to console.

```javascript
let AC = new AutoCapture();
AC.init({
    ...,
    onComplete: function(data) {
        console.log(data);
    }
});
```

### onError

This callback fires when an error occurs during the SDK lifecycle. This callback function is **required**. The `error` parameter is an object with two properties:

- **`code`** — A structured error code (e.g., `capture_error:4004`). Use the category prefix to determine the appropriate UI response. See [Error Codes](#error-codes) for the full list of categories.
- **`stack`** — A stack trace string. Include this when reporting issues to support for faster diagnosis.

Example Web (JS):

```javascript
let AC = new AutoCapture();
AC.init({
    ...,
    onError: function(error) {
        console.error(error.code, error.stack);

        if (error.code.startsWith('capture_error')) {
            // Camera issue — show retry UI or prompt for camera permission
        } else if (error.code.startsWith('init_error')) {
            // Initialization failed — prompt user to refresh the page
        }
        // Note: the SDK no longer raises a `not_allowed` error for unknown or
        // unrequested documents. It captures them (manually if needed);
        // document-type rejection happens server-side in DataChecker's document
        // classification service. See [Allowed documents](#allowed-documents).
    }
});
```

### onUserExit

This callback can be used to implement actions like returning users to the previous page or prompting them for confirmation before exiting to ensure they don't lose any unsaved data or work. This callback function is **required**. The `error` parameter within the function contains information about the specific error encountered, allowing you to log or display error messages for debugging or user guidance. The error that is thrown is `"exit"`.

Example Web (JS):

Within the example below we are logging the output (`error`) to console. Finally, we move back one page in the session history with `window.history.back()`.

```javascript
let AC = new AutoCapture();
AC.init({
    ...,
    onUserExit: function(error) {
        console.log(error);
        window.history.back()
    }
});
```

## Usage/Examples

The tool first needs to be initialised to load all the models.
Once its initialised, it will be started.

```javascript
let AC = new AutoCapture();
AC.init({
    CONTAINER_ID: ...,
    LANGUAGE: ...,
    TOKEN: ...,
    onComplete: ...,
    onError: ...,
    onUserExit: ...
});
```

To stop the camera and empty the container with its contents the `stop` function can be called. This function will automatically be called within `onComplete`, `onError` and `onUserExit` thus do not have to be called within your own custom versions of these functions.

```javascript
AC.stop();
```

If you wish to completely remove the container (identified by `CONTAINER_ID`), use the remove command:

```javascript
AC.remove();
```

Example below:

```javascript
let AC = new AutoCapture();
AC.init({
    CONTAINER_ID: 'AC_mount',
    LANGUAGE: 'nl',
    TOKEN: "<SDK_TOKEN>",
    onComplete: function(data) {
        console.log(data);
    },
    onError: function(error) {
        console.error(error.code, error.stack)
    },
    onUserExit: function(error) {
        console.log(error);
        window.history.back()
    }
});
```

## Importing SDK

Import the SDK with one of the three methods: Script tag, ES6 or CommonJS.

### Script Tag

Easily add AutoCapture to your HTML files using the Script Tag method.

```html
<!-- Add AutoCapture directly in your HTML -->
<script src="dist/autocapture.obf.js"></script>
```

### NPM

For projects using NPM and a module bundler like Webpack or Rollup, you can import AutoCapture as an ES6 module or with CommonJS require syntax.

```js
// Import AutoCapture in your JavaScript file

// ES6 style import
import AutoCapture from '@datachecker/autocapture';

// CommonJS style require
let AutoCapture = require('@datachecker/autocapture')
```

## Demo

```html
<!DOCTYPE html>
<html>
<head>
<title>AutoCapture</title>
</head>

<body>
    <div id="AC_mount"></div>
</body>

<script src="autocapture.obf.js" type="text/javascript"></script>

<script>
    let AC = new AutoCapture();
    AC.init({
        CONTAINER_ID: 'AC_mount',
        LANGUAGE: 'nl',
        TOKEN: "<SDK_TOKEN>",
        onComplete: function (data) {
            console.log(data)
        },
        onError: function(error) {
            console.error(error.code, error.stack);
        },
        onUserExit: function(error) {
            console.log(error);
            window.history.back()
        }
    });
</script>

</html>
```

For comprehensive integration examples, please refer to our [Integration Examples](examples/README.md).

## Languages

Notifications can be loaded in two ways: from a language file or from a JSON object.

### Loading from a file

Language files are located in `assets/language/`. The currently supported languages are:

- Bulgarian (bg)
- Czech (cz)
- German (de)
- English (en)
- Spanish (es)
- French (fr)
- Hungarian (hu)
- Italian (it)
- Lithuanian (lt)
- Dutch (nl)
- Papiamento (pa)
- Polish (pl)
- Portuguese (pt)
- Romanian (ro)
- Slovak (sk)
- Ukrainian (uk)

Additional languages can be added if needed.

You can load a language in the `configuration` by setting the `LANGUAGE` option:

```javascript
let AC = new AutoCapture();
AC.init({
    LANGUAGE: 'en',
    ...
```

To create support for a new language, a js file needs to be created with specific keys.
The keys can be derived from the current language js files (`assets/language/en.js`).

Example:

```javascript
var LANGUAGE = {
    approval_prompt: "Is the image right?",
    capture_error: "We were unable to capture an image. Camera access is required.",
    device_error: "This device is not supported.",
    camera_selection: "Please select the back camera",
    confirm: "Accept",
    continue: "Continue",
    corners: "Not all corners detected",
    exp_bright: "Environment is too bright",
    exp_dark: "Environment is too dark",
    flip: "Flip the document",
    flip_backside: "Flip the document to the backside",
    flip_frontside: "Flip the document to the frontside",
    focus: "Hold still...",
    glare: "Glare detected",
    init_error: "Initialization failed. Please refresh the page.",
    manual_mode: "Manual capture mode. Tap the button when ready.",
    model_error: "Failed to load required resources. Please check your connection.",
    occlusion: "Document is occluded",
    opencv_error: "A required component failed to load. Please refresh the page.",
    runtime_error: "Something went wrong. Please try again.",
    settings_error: "Configuration error. Please contact support.",
    size: "Move closer",
    start_prompt: "Tap to start",
    std_msg_0: "Place your document",
    retry: "Try again",
    rotate_phone: "Please rotate your phone upright",
    token_error: "Authorization failed. Please try again later.",
    tutorial: "Follow the instructions"
}
```

### Object (json)

Notifications can also be loaded as a json object like the following:

```javascript
let AC = new AutoCapture();
AC.init({
    LANGUAGE: JSON.stringify(
        {
            approval_prompt: "Is the image right?",
            capture_error: "We were unable to capture an image. Camera access is required.",
            device_error: "This device is not supported.",
            camera_selection: "Please select the back camera",
            confirm: "Accept",
            continue: "Continue",
            corners: "Not all corners detected",
            exp_bright: "Environment is too bright",
            exp_dark: "Environment is too dark",
            flip: "Flip the document",
            flip_backside: "Flip the document to the backside",
            flip_frontside: "Flip the document to the frontside",
            focus: "Hold still...",
            glare: "Glare detected",
            init_error: "Initialization failed. Please refresh the page.",
            manual_mode: "Manual capture mode. Tap the button when ready.",
            model_error: "Failed to load required resources. Please check your connection.",
            occlusion: "Document is occluded",
            opencv_error: "A required component failed to load. Please refresh the page.",
            runtime_error: "Something went wrong. Please try again.",
            settings_error: "Configuration error. Please contact support.",
            size: "Move closer",
            start_prompt: "Tap to start",
            std_msg_0: "Place your document",
            retry: "Try again",
            rotate_phone: "Please rotate your phone upright",
            token_error: "Authorization failed. Please try again later.",
            tutorial: "Follow the instructions"
        }
    ),
    ...
```

## Error Codes

The `onError` callback receives an object with four properties:

| Property  | Description                                                                                                                                                                                                                                                      |
| --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `chain`   | Array tracing the causal chain, outermost first and root cause last. Each entry has `name`, `message`, optional `code` and `stack`. Where a browser error caused the failure, such as a `DOMException` from the camera, it appears here with its original `name` |
| `code`    | Structured code in the form `category:NNNN`, e.g. `capture_error:4004`. Branch on the category prefix                                                                                                                                                            |
| `message` | Human-readable description of the failure                                                                                                                                                                                                                        |
| `stack`   | Stack trace string. Include it when reporting to support                                                                                                                                                                                                         |

Use the category prefix to determine the type of error and the appropriate response.

| Category         | Description                                                                                                           | Recommended Action                                                                                                          |
| ---------------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `capture_error`  | Camera failure. `4004` denied, `4007` no camera, `4008` camera in use, `4009` constraints unsatisfiable               | Retry is worth offering for `4004` and `4008`, not for `4007`. The original `DOMException` appears in the error's `chain`   |
| `device_error`   | Device cannot run the SDK: no WebAssembly (`7002`) or no WebAssembly SIMD (`7003`). Surfaced before start.            | Show "device not supported". Do **not** offer a retry; nothing will change. See [Unsupported devices](#unsupported-devices) |
| `init_error`     | Initialization failed                                                                                                 | Prompt user to refresh the page                                                                                             |
| `model_error`    | ML model failed to load                                                                                               | Check network connection, retry initialization                                                                              |
| `not_allowed`    | Deprecated: no longer emitted from capture (see [Allowed documents](#allowed-documents))                              | No action needed; validate document type on your backend                                                                    |
| `opencv_error`   | Required component failed to load                                                                                     | Prompt user to refresh or try a different browser                                                                           |
| `runtime_error`  | Unexpected/generic runtime error                                                                                      | Show generic retry UI                                                                                                       |
| `settings_error` | Invalid configuration or version mismatch. `2007` is an invalid `ALLOWED_DOCUMENTS`; its `message` lists every reason | Verify SDK configuration and assets                                                                                         |
| `token_error`    | Token missing, invalid, or not permitted                                                                              | Verify token credentials                                                                                                    |

When reporting issues to support, include both `error.code` and `error.stack` — the numeric identifier in the code and the stack trace allow for precise diagnosis.

The user-facing alert message shown in the SDK overlay is determined by the category prefix, which maps to a key in the [Languages](#languages) dictionary (e.g., `capture_error` maps to the `capture_error` language key). If a custom language file does not include a key for a new category (e.g., `init_error`), the SDK falls back to its built-in English default.

## Models

The tool uses a collection of neural networks. Make sure that you host the full directory so the models can be accessed. The models path can be configured. (see [Configuration](#configuration))
The models are located under `models/`.

## Allowed documents

The `ALLOWED_DOCUMENTS` setting describes the documents you request. It steers capture rather than blocking it: the SDK never rejects a document or interrupts the person with a rejection screen at capture time. Document-type rejection is not removed, it moves server-side: DataChecker's document classification service validates the captured document during processing and rejects unsupported types. The SDK is lenient; the backend is not.

It does two things:

1. **Auto-capture steering**: A confidently recognised, requested document (and the requested side, if you asked for one) auto-captures. Anything else (unknown type, or a recognised type you did not request) is not auto-captured; the person captures it with the manual capture button and the image goes to `onComplete`.

2. **Side permissions**: Which sides (front, back, or both) you request per type. This controls flipping and which side auto-captures.

### Configuration Examples

**Allow both sides of documents:**

```javascript
let AC = new AutoCapture();
AC.init({
    CONTAINER_ID: 'AC_mount',
    ALLOWED_DOCUMENTS: {
        IDENTITY_CARD: ['FRONT', 'BACK'],     // Both front and back sides allowed
        PASSPORT: ['FRONT', 'BACK'],          // Both front and back sides allowed
        DUTCH_PASSPORT: undefined,            // Optional: omit it entirely and it follows PASSPORT
        RESIDENCE_PERMIT: ['FRONT', 'BACK'],  // Both front and back sides allowed
        DRIVING_LICENSE: ['FRONT', 'BACK'],   // Both front and back sides allowed
    },
    TOKEN: "<SDK_TOKEN>",
    onComplete: function (data) {
        console.log(data)
    },
    onError: function(error) {
        console.error(error.code, error.stack)
    },
    onUserExit: function (error) {
        console.log(error)
        window.history.back()
    }
})
```

**Restrict to only front side:**

```javascript
ALLOWED_DOCUMENTS: {
    IDENTITY_CARD: ['FRONT'],   // Only the front auto-captures; the back is captured manually
    PASSPORT: ['FRONT'],        // Only the front auto-captures; the back is captured manually
}
```

**Note**: A type not listed in `ALLOWED_DOCUMENTS`, or the wrong side of a single-sided request, is not auto-captured, but the SDK does not reject it. The person can still capture it with the manual button; DataChecker's document classification service decides during processing.

**The object you pass replaces the defaults, it is not merged with them.** Whatever you leave out is not requested, with one exception: `DUTCH_PASSPORT` follows `PASSPORT` when left out (see [Dutch passports](#dutch-passports) below). If you want to change the sides for one type only, list every type you want auto-captured, not just the one you are changing. Passing `ALLOWED_DOCUMENTS: undefined` is the same as leaving the setting out entirely: the default set applies. (This holds for any optional setting; a required one passed as `undefined` is reported as an error.)

### Dutch passports

The SDK recognises a Dutch passport as its own type, `DUTCH_PASSPORT`, separately from `PASSPORT`. This exists so you can give Dutch passports different side rules from other passports, for example requesting only their front:

```javascript
ALLOWED_DOCUMENTS: {
    PASSPORT: ['FRONT', 'BACK'],   // Other passports: both sides
    DUTCH_PASSPORT: ['FRONT'],     // Dutch passports: front only
}
```

If you do not need that distinction, you do not have to think about it: **when you request `PASSPORT` and give `DUTCH_PASSPORT` no value of its own, Dutch passports follow your `PASSPORT` setting.** Leaving the key out entirely, or setting it to `undefined` or `null`, all mean the same thing. So this requests every passport, Dutch ones included:

```javascript
ALLOWED_DOCUMENTS: {
    IDENTITY_CARD: ['FRONT'],
    PASSPORT: ['FRONT'],           // Dutch passports follow this
}
```

Set `DUTCH_PASSPORT` explicitly only when you want it to differ from `PASSPORT`. It is the one type that can be requested without being listed, so requesting `PASSPORT` always requests Dutch passports too, on the same sides unless you say otherwise.

### Handling unknown documents

When the SDK cannot confidently classify a document, both its type and page ID are `'UNK'`. An unknown document is not auto-captured and does **not** raise an error: the person captures it with the manual button (shown by default after `CAPTURE_BTN_AFTER` ms), with a flip prompt so both sides are collected, and the images go to `onComplete`.

### Future-proofing with the manual capture button

The on-device classifier recognises a fixed set of document types, but new and updated documents keep appearing. Any on-device type check will eventually meet a valid document it was not trained on, and rejecting it at capture would block real users until a new SDK ships and every integrator upgrades. AutoCapture avoids this:

- **On-device classification only steers** (animation, expected side, whether to auto-capture); it is not the final gate.
- **The manual capture button is the escape hatch**, shown automatically after `CAPTURE_BTN_AFTER` (default 10s), so no one is dead-ended on an unrecognised document.
- **DataChecker's document classification service is the source of truth.** It runs server-side, rejects unsupported documents during processing, and is maintained and updated as new documents appear, without an SDK release.

So supporting a new document type is a server-side change, not an SDK rollout. Keep `CAPTURE_BTN_AFTER` on (the default); set it to `0` only to accept exactly what the on-device model recognises and nothing else.

## Output

The SDK will output in the following structure:

```json
{
    "image": ["...base64_img"],
    "meta": [
        {
            "angle": "...",
            "coordinates": [
                ["...", "..."],
                ["...", "..."],
                ["...", "..."],
                ["...", "..."]
            ],
            "force_capture": "...",
            "device": "..."
        }
    ],
    "token": "sdk_token"
}
```

Example:

```json
{
    "image": ["iVBORw0KGgoAAAANSUhEUgAAAysAAAS..."],
    "meta": [
        {
            "angle": 0,
            "coordinates": [
                [0, 0],
                [0, 100],
                [150, 100],
                [150, 0]
            ],
            "force_capture": false,
            "device": "Back Camera"
        }
    ],
    "token": "sdk_token"
}
```
