> ## Documentation Index
> Fetch the complete documentation index at: https://developers.argosidentity.com/llms.txt
> Use this file to discover all available pages before exploring further.

# FaceAuth Common Reference

> The FaceAuthSubmission object and error response format shared by every FaceAuth API endpoint, plus what changes when moving from the previous endpoint (/v3/faceauth) to /v3/face-auth.

<Warning>
  **The FaceAuth API has moved to `/v3/face-auth`.**

  The response structure, field names, and error format all differ from the previous endpoint `/v3/faceauth`. To move an existing integration, start with [Migrating from the previous API](#migrating-from-the-previous-api) on this page.
</Warning>

This page collects the definitions shared by several endpoints. Each endpoint page references these definitions by name and documents only what differs on that endpoint.

* [FaceAuthSubmission object](#faceauthsubmission-object) — used by the [POST](/en/idcheck/add-on/post-faceauth) creation response, the `items[]` elements of the [list lookup](/en/idcheck/add-on/get-faceauth), and the [single lookup](/en/idcheck/add-on/get-faceauth_detail) response.
* [Error responses](#error-responses) — the error format shared by every endpoint.
* [Migrating from the previous API](#migrating-from-the-previous-api) — what changes when moving a `/v3/faceauth` integration to `/v3/face-auth`.

## FaceAuthSubmission object

An object holding the information for a single FaceAuth submission: the authentication result, the policy used for the decision, and the decision scores.

<Warning>
  **Fields are never omitted from responses.**

  Policies that were not applied, results that were not collected, and arrays that do not apply are returned with `null` instead of having the key removed. The set of keys in a response body is therefore **always the same**, regardless of submission type or project settings, and can be checked with `value === null` alone — no `'field' in obj` or `undefined` checks needed.

  Fields whose type carries `nullable` are the ones that can be `null`.
</Warning>

### Top-level fields

<ResponseField name="authId" type="string">
  The key of the FaceAuth submission. Store it — it is used by the [single lookup](/en/idcheck/add-on/get-faceauth_detail), [image download](/en/idcheck/add-on/get-faceauth_image), and [delete](/en/idcheck/add-on/delete-faceauth) APIs.
</ResponseField>

<ResponseField name="authStatus" type="string">
  The authentication result. One of `approved` or `rejected`.
</ResponseField>

<ResponseField name="createTime" type="string">
  When the submission was created, in `yyyy-MM-dd'T'HH:mm:ss.SSSZ` RFC 3339 UTC format. (For example, `2026-08-11T10:09:14.028Z`)
</ResponseField>

<ResponseField name="submitType" type="string">
  How the submission was made. `form` means the Face Auth URL screen, `api` means [POST /v3/face-auth](/en/idcheck/add-on/post-faceauth).
</ResponseField>

<ResponseField name="kycSubmissionId" type="string">
  The ID of the KYC submission used as the comparison reference.
</ResponseField>

<ResponseField name="deleteTime" type="nullable · string">
  When the deletion completed, in RFC 3339 UTC format. `null` for submissions that have not been deleted.
</ResponseField>

<ResponseField name="policy" type="object">
  The policy used for the decision. All six items are always present. See [policy items](#policy-items) below.
</ResponseField>

<ResponseField name="result" type="object">
  The decision results. All six fields are always present. See [result fields](#result-fields) below.
</ResponseField>

<ResponseField name="signals" type="nullable · object">
  Supplementary information collected on the Face Auth URL screen. See [signals fields](#signals-fields) below.
</ResponseField>

<ResponseField name="rejectComment" type="nullable · array">
  An array of rejection reason strings. `null` for `approved` submissions.
</ResponseField>

<ResponseField name="failCode" type="nullable · array">
  An array of rejection code strings. `null` for `approved` submissions.
</ResponseField>

<Note>
  The [POST /v3/face-auth](/en/idcheck/add-on/post-faceauth) creation response does **not** include `policy`. Every other field is the same.
</Note>

### policy items

A snapshot of the project settings at processing time. Changing the project settings afterwards does not change the values on submissions that already exist.

All six items (`faceSimilarity`, `occluded`, `faceCover`, `headCover`, `liveness`, `activeLiveness`) carry the two fields below.

<ResponseField name="enabled" type="boolean">
  Whether this policy was turned on when the submission was processed. Determine whether a policy applied from this value, not from `threshold`.
</ResponseField>

<ResponseField name="threshold" type="nullable · number">
  The numeric criterion used for the decision. `null` when `enabled` is `false` or when the policy does not use a numeric criterion.
</ResponseField>

| Item             | Policy                             | Pass condition                                                                                       |
| ---------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `faceSimilarity` | Face similarity                    | `result.faceSimilarity` is at or above `threshold`                                                   |
| `occluded`       | No face occlusion                  | The face is not covered. Uses no numeric criterion, so `threshold` is always `null`                  |
| `faceCover`      | Face protection equipment required | A mask, goggles, or similar is detected with confidence at or above `threshold`                      |
| `headCover`      | Head protection equipment required | A helmet or similar is detected with confidence at or above `threshold`                              |
| `liveness`       | Passive Liveness                   | `result.livenessScore` is at or above `threshold`                                                    |
| `activeLiveness` | Active Liveness                    | `result.activeLivenessScore` is at or above `threshold`. Runs only on `submitType: form` submissions |

<Warning>
  **`occluded` and `faceCover` / `headCover` have opposite purposes.**

  `occluded` forbids covering the face, while `faceCover` and `headCover` require protective equipment to be worn. The names look similar but the pass conditions are the reverse of each other. The latter two exist for environments such as industrial sites where protective equipment is required.
</Warning>

<Note>
  **`enabled` is a project setting, not an execution result.**

  A policy that is turned on may still not run, depending on the submission method. Whether it actually ran is determined by the corresponding field in `result`: `null` means it did not run. Today the only place this difference appears is `activeLiveness`. A `submitType: api` submission returns `result.activeLivenessScore` as `null` even when `enabled` is `true`, and that is expected.
</Note>

### result fields

The result field of a policy that did not run is `null`. For example, a submission with `policy.liveness.enabled` set to `false` has `livenessScore` as `null`.

<ResponseField name="faceSimilarity" type="nullable · number">
  The face similarity score, between 0 and 100 inclusive. May include decimals.
</ResponseField>

<ResponseField name="livenessScore" type="nullable · number">
  The Passive Liveness score.
</ResponseField>

<ResponseField name="activeLivenessScore" type="nullable · number">
  The Active Liveness score. Always `null` for `submitType: api` submissions.
</ResponseField>

<ResponseField name="occluded" type="nullable · object">
  The face occlusion detection result. `value: true` means the face was judged covered and the submission was rejected.

  <Expandable title="Properties">
    <ResponseField name="value" type="nullable · boolean">
      Whether it was detected.
    </ResponseField>

    <ResponseField name="confidence" type="nullable · number">
      The decision confidence, between 0 and 100 inclusive.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="faceCover" type="nullable · object">
  The face protection equipment detection result. `value: false` means it was judged not worn and the submission was rejected. Its properties are the same as `occluded`.
</ResponseField>

<ResponseField name="headCover" type="nullable · object">
  The head protection equipment detection result. `value: false` means it was judged not worn and the submission was rejected. Its properties are the same as `occluded`.
</ResponseField>

<Note>
  `occluded`, `faceCover`, and `headCover` share the same structure. When the check did not run, **the whole object is `null`** — it is never returned with only its properties set to `null`.
</Note>

### signals fields

Supplementary information collected on the Face Auth URL screen. Only `submitType: form` submissions with collected values carry data here; `submitType: api` submissions and submissions with nothing collected have **the whole object as `null`**. It is informational and is not used for the authentication decision.

<ResponseField name="startButtonClickTime" type="nullable · string">
  When the user pressed the start button, in RFC 3339 UTC format.
</ResponseField>

<ResponseField name="cameraProcessInfo" type="nullable · array">
  A record of camera capture segments. When there is no record this is `null`, not an empty array.

  <Expandable title="Properties">
    <ResponseField name="[i].processStartTime" type="nullable · string">
      When the capture segment started, in RFC 3339 UTC format.
    </ResponseField>

    <ResponseField name="[i].processEndTime" type="nullable · string">
      When the capture segment ended, in RFC 3339 UTC format.
    </ResponseField>

    <ResponseField name="[i].type" type="nullable · string">
      The kind of capture segment, such as `faceAuth-passive`, `faceAuth-active`, or `faceAuth-auto-capture`. The list of values is not fixed and may be extended without notice, so ignore values you do not recognise.
    </ResponseField>

    <ResponseField name="[i].error" type="nullable · string">
      An error that occurred during that segment, or `null` if there was none. The message is collected from the user's browser and has no fixed format, so do not branch on it.
    </ResponseField>
  </Expandable>
</ResponseField>

### rejectComment and failCode

`rejectComment` is a human-readable explanation; `failCode` is the code to branch on.

<Warning>
  **Branch on `failCode`, not `rejectComment`.** The wording of `rejectComment` changes without notice, and different causes sometimes share the same wording. `Please retry with another face image.` is used by both `liveness_fail` and `active_liveness_fail`.
</Warning>

The two arrays **correspond index by index and always have the same length.** The reason for `failCode[0]` is `rejectComment[0]`.

```json result.json theme={null}
{
  "rejectComment": [
    "Protection equipment is not found on Head.",
    "face compare similarity score is lower than threshold"
  ],
  "failCode": ["Head_cover_fail", "face_compare_underscore"]
}
```

A single submission can fail several policies at once, so check every value in the array. The array order follows the policy evaluation order and is not fixed — do not assume a particular code always comes first.

For the list of `failCode` values, see [POST /v3/face-auth — Rejection codes](/en/idcheck/add-on/post-faceauth#5-3-rejection-codes-failcode).

### Parsing notes

* The set of keys in a response is always the same. Missing values come back as `null`, so `undefined` checks and key-existence checks are unnecessary.
* `0` is a valid value for `policy.*.threshold`. Do not use a falsy check to decide whether a policy is configured — use `enabled`.
* Array fields are `null`, not an empty array, when there is no value. Default them with `?? []` or check `!== null` before iterating.
* Nested objects (`occluded`, `faceCover`, `headCover`, `signals`) are `null` as a whole when they do not apply. Check the parent for `null` before reading its properties.
* When a threshold and a score are equal, the threshold is considered met.
* Scores may include decimals rather than being integers. Parse them as numbers, not strings.

## Error responses

Errors returned by the ARGOS application use the format below.

```json error.json theme={null}
{
  "code": "REQUEST_INVALID",
  "message": "limit must be a decimal integer between 10 and 200."
}
```

<ResponseField name="code" type="string">
  The error code. Branch on this value rather than on the HTTP status code.
</ResponseField>

<ResponseField name="message" type="string">
  A human-readable description. The wording may change without notice, so never branch on string comparison.
</ResponseField>

The error object has exactly two fields: `code` and `message`.

### Authentication layer errors (401, 403)

<Warning>
  **Requests blocked at the authentication layer do not follow the `{ code, message }` format.**

  API key validation happens at the authentication layer, before the request reaches the ARGOS application. Responses blocked at that stage have no `code` field.
</Warning>

| Situation                                              | HTTP status | Response body                                    |
| ------------------------------------------------------ | ----------- | ------------------------------------------------ |
| No API key sent                                        | `401`       | `{ "message": "Unauthorized" }`                  |
| Nonexistent or invalid API key                         | `403`       | `{ "message": "..." }`                           |
| Valid API key that is not linked to a FaceAuth project | `403`       | `{ "code": "AUTH_FORBIDDEN", "message": "..." }` |

* **Handle `401` and authentication-layer `403` by HTTP status, not by `code`.** Neither response has a `code` field.
* `403` has two causes with different response formats. If a `code` field is present the application returned it; if not, the authentication layer blocked the request.
* The `message` of an authentication-layer error is not generated by ARGOS and may change without notice. Do not parse it.

<Note>
  When reporting a technical question or an incident, include the request time (UTC), the endpoint called, and the first four characters of the API key used.
</Note>

### Error code list

| HTTP status | code                            | Condition                                                                       | Endpoints                    |
| ----------- | ------------------------------- | ------------------------------------------------------------------------------- | ---------------------------- |
| `400`       | `REQUEST_INVALID`               | The request is invalid. See the error section of each endpoint page.            | All                          |
| `400`       | `FACEAUTH_FACE_NOT_DETECTED`    | No comparable face was found in the face image.                                 | POST                         |
| `403`       | `AUTH_FORBIDDEN`                | The API key is valid but is not linked to a FaceAuth project.                   | All                          |
| `404`       | `FACEAUTH_SUBMISSION_NOT_FOUND` | The `authId` cannot be found in this project.                                   | Single lookup, image, DELETE |
| `404`       | `SUBMISSION_NOT_FOUND`          | The KYC submission does not exist, was deleted, or belongs to another project.  | POST                         |
| `409`       | `SUBMISSION_NOT_APPROVED`       | The KYC submission is not approved or has no reference image.                   | POST                         |
| `503`       | `UPSTREAM_UNAVAILABLE`          | An internal service is temporarily unavailable. Retry the same request shortly. | All                          |
| `503`       | `UPSTREAM_INVALID_RESPONSE`     | The internal lookup result cannot be converted to the public contract.          | List and single lookup       |
| `500`       | `INTERNAL_UNEXPECTED`           | An unexpected server error.                                                     | All                          |

## Migrating from the previous API

If you integrate against the previous endpoint `/v3/faceauth`, review the changes below. **The paths, field names, response envelopes, and error format have all changed.**

### Endpoints

| Operation           | Previous API                        | New API                            |
| ------------------- | ----------------------------------- | ---------------------------------- |
| Create a submission | `POST /v3/faceauth`                 | `POST /v3/face-auth`               |
| List lookup         | `GET /v3/faceauth`                  | `GET /v3/face-auth`                |
| Single lookup       | `GET /v3/faceauth?authId=...`       | `GET /v3/face-auth/{authId}`       |
| Image download      | `GET /v3/faceauth/image?authId=...` | `GET /v3/face-auth/{authId}/image` |
| Delete a submission | `DELETE /v3/faceauth?authId=...`    | `DELETE /v3/face-auth/{authId}`    |

`authId` is now a **path parameter** rather than a query parameter, and the list lookup and single lookup are **separate endpoints**.

### Response envelopes

| Case                | Previous API                                            | New API                                          |
| ------------------- | ------------------------------------------------------- | ------------------------------------------------ |
| Single lookup       | `{ faceAuth_projectId, data: [ {...} ] }`               | The submission object at the top level           |
| List lookup         | `{ faceAuth_projectId, data: { items, nextPage_key } }` | `{ items, nextCursor }`                          |
| Unknown `authId`    | `200` with `data: []`                                   | `404 FACEAUTH_SUBMISSION_NOT_FOUND`              |
| Create a submission | `200` with `{ score, authentication_id, ... }`          | `201 Created` with the FaceAuthSubmission object |
| Delete a submission | `{ "result": "success", "statusCode": 200 }`            | `{ "authId": "..." }`                            |

`faceAuth_projectId` and `statusCode` have been removed from responses.

### Field names

| Previous API                                 | New API                                     |
| -------------------------------------------- | ------------------------------------------- |
| `auth_id` / `authentication_id` (POST)       | `authId`                                    |
| `auth_status`                                | `authStatus`                                |
| `create_time`                                | `createTime`                                |
| `submit_type`                                | `submitType`                                |
| `kyc_submission_id`                          | `kycSubmissionId`                           |
| `reject_comment` / `rejected_comment` (POST) | `rejectComment`                             |
| `fail_code`                                  | `failCode`                                  |
| `score` (POST, snake\_case)                  | `result` (camelCase, same as lookups)       |
| `delete_check` + `delete_time`               | `deleteTime` alone (`null` before deletion) |

### Structural changes

<AccordionGroup>
  <Accordion title="policy — from flat thresholds to nested objects">
    The previous API carried thresholds flat, as in `faceSimilarity_threshold: 85`, and only `occluded_threshold` was a `boolean`.

    The new API gives each item an `{ enabled, threshold }` object. **Whether a policy applied is determined by `enabled`, not by the value of `threshold`.** `occluded` uses no numeric criterion, so its `threshold` is always `null`.

    ```json Previous API theme={null}
    { "faceSimilarity_threshold": 85, "occluded_threshold": true }
    ```

    ```json New API theme={null}
    {
      "faceSimilarity": { "enabled": true, "threshold": 85 },
      "occluded": { "enabled": true, "threshold": null }
    }
    ```
  </Accordion>

  <Accordion title="result — occluded, faceCover, and headCover are objects">
    The previous API returned a single value such as `occluded: false` (boolean) or `headCover: 0` (number). In the new API all three are `{ value, confidence }` objects, and the whole object is `null` when the check did not run.

    ```json New API theme={null}
    { "occluded": { "value": false, "confidence": 99.2 }, "faceCover": null }
    ```
  </Accordion>

  <Accordion title="Pagination — from a composite key to a single cursor">
    The previous API returned `nextPage_key` and expected its `authId` and `createTime` back as `nextKey_id` and `nextKey_date`, with the page size set by `count` (1–2,000, default 2,000).

    The new API returns a single signed string, `nextCursor`, which you pass back verbatim as `cursor`, with the page size set by `limit` (10–200, default 100). A `limit` outside the range is no longer ignored — it returns `400 REQUEST_INVALID`.
  </Accordion>

  <Accordion title="Image download — from a binary body to a 302 redirect">
    The previous API returned the JPEG binary directly in the response body. The new API returns `302 Found` with a short-lived download URL in the `Location` header, so the **HTTP client must be configured to follow redirects** (`curl --location`).
  </Accordion>

  <Accordion title="Error format — from three formats to one">
    The previous API mixed `{ message, errorCode, statusCode }`, `{ traceId, errorCode, message }`, and `{ message, statusCode }` across endpoints, and the image endpoint returned every authentication failure as `403`.

    The new API returns all application errors as `{ code, message }`, with authentication-layer errors as the only exception. See [Error responses](#error-responses) above.
  </Accordion>
</AccordionGroup>

### The change to watch most closely

<Warning>
  **The previous API dropped the keys of unused options from responses; the new API always includes the key and fills the value with `null`.**

  Code written against the previous API that decides whether a policy was used from key existence — `if (result.headCover)` or `'headCover' in result` — **misbehaves against the new API.** Use `result.headCover !== null` (whether it ran) or `policy.headCover.enabled` (whether it is configured) instead.
</Warning>
