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

# Node Reference

> Complete reference for all FaceSign verification node types — parameters, outcomes, and configuration.

This reference documents every FaceSign node type with its full parameter set, outcome keys, and configuration options.

<Note>
  For a conceptual overview of how nodes work and connect, see [Node Model](/docs/reference/concepts#nodes). For common flow patterns, see [Verification Patterns](/docs/guides/verification-patterns).
</Note>

## Base node structure

All nodes share these fields:

| Field  | Type     | Required | Description                                            |
| ------ | -------- | -------- | ------------------------------------------------------ |
| `id`   | `string` | Yes      | Unique identifier within the flow. You assign this.    |
| `type` | `string` | Yes      | The node type. Must be one of the values listed below. |

## Node type enum

```typescript wrap theme={null}
enum FSNodeType {
  START = "start",
  END = "end",
  CONVERSATION = "conversation",
  LIVENESS_DETECTION = "liveness_detection",
  FACE_SCAN = "face_scan",
  DOCUMENT_SCAN = "document_scan",
  RECOGNITION = "recognition",
  ENTER_EMAIL = "enter_email",
  TWO_FACTOR_EMAIL = "two_factor_email",
  TWO_FACTOR_SMS = "two_factor_sms",
  DATA_VALIDATION = "data_validation",
  FACE_COMPARE = "face_compare",
  PERMISSIONS = "permissions"
}
```

***

## START

Entry point for every flow. Exactly one START node is required per flow.

**Additional fields:**

| Field     | Type     | Required | Description                           |
| --------- | -------- | -------- | ------------------------------------- |
| `outcome` | `string` | Yes      | The `id` of the next node to execute. |

**Example:**

```json wrap theme={null}
{
  "id": "start_node",
  "type": "start",
  "outcome": "welcome_conversation"
}
```

***

## END

Exit point for a flow. At least one END node is required. The END node has no outcomes -- reaching it marks the session as complete.

**Additional fields:** None.

**Example:**

```json wrap theme={null}
{
  "id": "end_node",
  "type": "end"
}
```

***

## CONVERSATION

AI-powered conversational interaction. The avatar speaks the prompt and waits for the user to respond. FaceSign's AI evaluates the response against the defined conditions and selects the matching outcome.

**Additional fields:**

| Field                 | Type      | Required | Description                                                                                                                                |
| --------------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `prompt`              | `string`  | Yes      | The message the avatar speaks. Use `Say: ...` prefix for direct speech. Supports `{name}`, `{email}` template variables from session data. |
| `outcomes`            | `array`   | Yes      | Non-empty array of conditional outcomes (see below).                                                                                       |
| `doesNotRequireReply` | `boolean` | No       | If `true`, the node advances immediately without waiting for a user response. Useful for farewell messages before an END node.             |

**Outcome structure (FSConditionalOutcome):**

| Field          | Type     | Required | Description                                                                 |
| -------------- | -------- | -------- | --------------------------------------------------------------------------- |
| `id`           | `string` | Yes      | Unique identifier for this outcome.                                         |
| `targetNodeId` | `string` | Yes      | The `id` of the next node if this condition matches.                        |
| `condition`    | `string` | Yes      | Natural-language description of when this outcome applies. Evaluated by AI. |

<Note>
  Conditions are evaluated in order. The first matching condition wins. Write conditions so they are mutually exclusive when possible.
</Note>

**Example:**

```json wrap theme={null}
{
  "id": "ask_name",
  "type": "conversation",
  "prompt": "Say: Hi there, could you tell me your full name?",
  "outcomes": [
    {
      "id": "tr_has_name",
      "targetNodeId": "verify_email",
      "condition": "The user provided their name"
    },
    {
      "id": "tr_no_name",
      "targetNodeId": "ask_name_again",
      "condition": "The user did not provide their name"
    }
  ]
}
```

***

## LIVENESS\_DETECTION

Anti-spoofing check to confirm a real person is present. Detects photos, videos, and deepfakes.

**Additional fields:**

| Field      | Type     | Required | Description                    |
| ---------- | -------- | -------- | ------------------------------ |
| `outcomes` | `object` | Yes      | Fixed outcome map (see below). |

**Outcomes:**

| Key                | Description                                               |
| ------------------ | --------------------------------------------------------- |
| `livenessDetected` | Node `id` to execute if a live person is confirmed.       |
| `deepfakeDetected` | Node `id` to execute if a deepfake or replay is detected. |
| `noFace`           | Node `id` to execute if no face is found in the frame.    |

**Example:**

```json wrap theme={null}
{
  "id": "liveness_check",
  "type": "liveness_detection",
  "outcomes": {
    "livenessDetected": "proceed_to_document",
    "deepfakeDetected": "verification_failed",
    "noFace": "retry_liveness"
  }
}
```

***

## FACE\_SCAN

Captures the user's face and performs 1:1 matching against a reference image (e.g., from a scanned document or a provided photo).

**Additional fields:**

| Field                      | Type      | Required | Description                                                                |
| -------------------------- | --------- | -------- | -------------------------------------------------------------------------- |
| `outcomes`                 | `object`  | Yes      | Fixed outcome map (see below).                                             |
| `referenceImageKey`        | `string`  | No       | Key in the session's `providedData` object containing the reference image. |
| `similarityThreshold`      | `number`  | No       | Match threshold from 0 to 1. Default: `0.85`.                              |
| `captureInstructions`      | `string`  | No       | Custom instruction text shown to the user during capture.                  |
| `requireLivenessChallenge` | `boolean` | No       | Whether to require an active liveness challenge during capture.            |
| `requireAILivenessCheck`   | `boolean` | No       | Whether to run an AI-based liveness check on the captured image.           |
| `enableSound`              | `boolean` | No       | Enable a capture sound effect.                                             |
| `enableHaptics`            | `boolean` | No       | Enable haptic feedback on supported devices.                               |

**Outcomes:**

| Key         | Description                                                      |
| ----------- | ---------------------------------------------------------------- |
| `passed`    | Node `id` if the face matches the reference above the threshold. |
| `notPassed` | Node `id` if the face does not match.                            |
| `cancelled` | Node `id` if the user cancels or exits the capture.              |
| `error`     | Node `id` if a technical error occurs.                           |

**Example:**

```json wrap theme={null}
{
  "id": "face_matching",
  "type": "face_scan",
  "outcomes": {
    "passed": "verification_complete",
    "notPassed": "retry_or_review",
    "cancelled": "flow_cancelled",
    "error": "technical_error"
  },
  "referenceImageKey": "primary_doc_face",
  "similarityThreshold": 0.90,
  "captureInstructions": "Look straight into the camera",
  "requireLivenessChallenge": true
}
```

***

## DOCUMENT\_SCAN

Captures and verifies an identity document. Supports passports, driver's licenses, and national ID cards.

**Additional fields:**

| Field                    | Type      | Required | Description                                                                                                |
| ------------------------ | --------- | -------- | ---------------------------------------------------------------------------------------------------------- |
| `outcomes`               | `object`  | Yes      | Fixed outcome map (see below).                                                                             |
| `scanningMode`           | `string`  | Yes      | `"single"` scans one side only. `"automatic"` detects and scans all required sides based on document type. |
| `allowedDocumentTypes`   | `array`   | Yes      | Array of allowed document types: `"passport"`, `"driver_license"`, `"id_card"`.                            |
| `showTorchButton`        | `boolean` | No       | Show a flashlight toggle in the UI.                                                                        |
| `showCameraSwitch`       | `boolean` | No       | Allow the user to switch between front and rear cameras.                                                   |
| `showMirrorCameraButton` | `boolean` | No       | Allow mirrored camera mode.                                                                                |

**Outcomes:**

| Key             | Description                              |
| --------------- | ---------------------------------------- |
| `scanSuccess`   | Node `id` on a successful document scan. |
| `userCancelled` | Node `id` if the user cancels the scan.  |
| `scanTimeout`   | Node `id` if the scan times out.         |

**Example:**

```json wrap theme={null}
{
  "id": "document_verification",
  "type": "document_scan",
  "scanningMode": "automatic",
  "allowedDocumentTypes": ["passport", "driver_license", "id_card"],
  "outcomes": {
    "scanSuccess": "face_matching",
    "userCancelled": "handle_cancellation",
    "scanTimeout": "retry_scan"
  },
  "showTorchButton": true
}
```

***

## RECOGNITION

1:N face recognition. Checks whether the user's face matches any existing entry in your FaceSign database.

**Additional fields:**

| Field      | Type     | Required | Description                    |
| ---------- | -------- | -------- | ------------------------------ |
| `outcomes` | `object` | Yes      | Fixed outcome map (see below). |

**Outcomes:**

| Key          | Description                                                 |
| ------------ | ----------------------------------------------------------- |
| `recognized` | Node `id` if the user matches a known face in the database. |
| `newUser`    | Node `id` if no match is found (new user).                  |
| `noFace`     | Node `id` if no face is detected.                           |

**Example:**

```json wrap theme={null}
{
  "id": "check_existing_user",
  "type": "recognition",
  "outcomes": {
    "recognized": "welcome_back",
    "newUser": "onboard_new_user",
    "noFace": "retry_capture"
  }
}
```

***

## ENTER\_EMAIL

Presents a form for the user to enter their email address.

**Additional fields:**

| Field      | Type     | Required | Description                    |
| ---------- | -------- | -------- | ------------------------------ |
| `outcomes` | `object` | Yes      | Fixed outcome map (see below). |

**Outcomes:**

| Key            | Description                                   |
| -------------- | --------------------------------------------- |
| `emailEntered` | Node `id` after the user submits their email. |
| `canceled`     | Node `id` if the user cancels.                |

**Example:**

```json wrap theme={null}
{
  "id": "collect_email",
  "type": "enter_email",
  "outcomes": {
    "emailEntered": "send_otp",
    "canceled": "handle_cancel"
  }
}
```

***

## TWO\_FACTOR\_EMAIL

Sends a one-time passcode (OTP) via email and verifies the user enters the correct code.

**Additional fields:**

| Field                | Type      | Required | Description                                                   |
| -------------------- | --------- | -------- | ------------------------------------------------------------- |
| `outcomes`           | `object`  | Yes      | Fixed outcome map (see below).                                |
| `otpLength`          | `number`  | No       | Number of digits in the OTP. Default: `6`.                    |
| `expirySeconds`      | `number`  | No       | How long the OTP remains valid, in seconds.                   |
| `maxAttempts`        | `number`  | No       | Maximum number of entry attempts allowed.                     |
| `resendAfterSeconds` | `number`  | No       | Minimum seconds before the user can request a new OTP.        |
| `showUI`             | `boolean` | No       | Whether to display the OTP entry UI. Default: `true`.         |
| `emailTemplate`      | `string`  | No       | Custom email body template. Use `{{otp}}` as the placeholder. |

**Outcomes:**

| Key                 | Description                                                        |
| ------------------- | ------------------------------------------------------------------ |
| `verified`          | Node `id` on successful OTP verification.                          |
| `delivery_failed`   | Node `id` if the email could not be delivered.                     |
| `failed_unverified` | Node `id` if the user exhausts attempts or enters an invalid code. |
| `cancelled`         | Node `id` if the user cancels.                                     |
| `error`             | Node `id` on a general error.                                      |

**Example:**

```json wrap theme={null}
{
  "id": "email_2fa",
  "type": "two_factor_email",
  "otpLength": 6,
  "expirySeconds": 300,
  "maxAttempts": 5,
  "resendAfterSeconds": 60,
  "outcomes": {
    "verified": "access_granted",
    "delivery_failed": "technical_support",
    "failed_unverified": "retry_email_2fa",
    "cancelled": "flow_cancelled",
    "error": "technical_error"
  }
}
```

***

## TWO\_FACTOR\_SMS

Sends a one-time passcode (OTP) via SMS and verifies the user enters the correct code.

**Additional fields:**

| Field                | Type      | Required | Description                                                 |
| -------------------- | --------- | -------- | ----------------------------------------------------------- |
| `outcomes`           | `object`  | Yes      | Fixed outcome map (see below).                              |
| `otpLength`          | `number`  | No       | Number of digits in the OTP. Default: `6`.                  |
| `expirySeconds`      | `number`  | No       | How long the OTP remains valid, in seconds.                 |
| `maxAttempts`        | `number`  | No       | Maximum number of entry attempts allowed.                   |
| `resendAfterSeconds` | `number`  | No       | Minimum seconds before the user can request a new OTP.      |
| `showUI`             | `boolean` | No       | Whether to display the OTP entry UI. Default: `true`.       |
| `smsTemplate`        | `string`  | No       | Custom SMS body template. Use `{{otp}}` as the placeholder. |

**Outcomes:**

| Key                 | Description                                                        |
| ------------------- | ------------------------------------------------------------------ |
| `verified`          | Node `id` on successful OTP verification.                          |
| `delivery_failed`   | Node `id` if the SMS could not be delivered.                       |
| `failed_unverified` | Node `id` if the user exhausts attempts or enters an invalid code. |
| `cancelled`         | Node `id` if the user cancels.                                     |
| `error`             | Node `id` on a general error.                                      |

**Example:**

```json wrap theme={null}
{
  "id": "sms_2fa",
  "type": "two_factor_sms",
  "otpLength": 6,
  "expirySeconds": 300,
  "maxAttempts": 5,
  "resendAfterSeconds": 60,
  "outcomes": {
    "verified": "access_granted",
    "delivery_failed": "technical_support",
    "failed_unverified": "retry_sms_2fa",
    "cancelled": "flow_cancelled",
    "error": "technical_error"
  }
}
```

***

## DATA\_VALIDATION

Performs conditional routing based on collected session data. Evaluates a validation rule against a data field and routes through transitions.

**Additional fields:**

| Field         | Type     | Required | Description                                           |
| ------------- | -------- | -------- | ----------------------------------------------------- |
| `validation`  | `object` | Yes      | The validation rule to evaluate (see below).          |
| `transitions` | `array`  | Yes      | Ordered array of conditional transitions (see below). |

**Validation object:**

| Field    | Type     | Required | Description                                                                             |
| -------- | -------- | -------- | --------------------------------------------------------------------------------------- |
| `field`  | `string` | Yes      | The data field to validate (e.g., `"age"`, `"email"`).                                  |
| `action` | `string` | Yes      | The validation operation: `"exists"`, `"matches"`, `"contains"`, `"greater_than"`, etc. |
| `value`  | `string` | No       | The expected value for comparison.                                                      |

**Transition structure:**

| Field          | Type     | Required | Description                                          |
| -------------- | -------- | -------- | ---------------------------------------------------- |
| `id`           | `string` | Yes      | Unique identifier for this transition.               |
| `targetNodeId` | `string` | Yes      | The `id` of the next node if this condition matches. |
| `condition`    | `string` | Yes      | Description of when this transition applies.         |

<Note>
  Transitions are evaluated in order. The first matching condition determines the next node. Make sure your conditions cover all cases.
</Note>

**Example:**

```json wrap theme={null}
{
  "id": "validate_age",
  "type": "data_validation",
  "validation": {
    "field": "age",
    "action": "greater_than",
    "value": "18"
  },
  "transitions": [
    {
      "id": "adult",
      "targetNodeId": "full_verification",
      "condition": "age >= 18"
    },
    {
      "id": "minor",
      "targetNodeId": "parental_consent",
      "condition": "age < 18"
    }
  ]
}
```

***

## FACE\_COMPARE

Compares two face images captured during the session to determine whether they belong to the same person.

**Additional fields:**

| Field      | Type     | Required | Description                    |
| ---------- | -------- | -------- | ------------------------------ |
| `outcomes` | `object` | Yes      | Fixed outcome map (see below). |

**Outcomes:**

| Key       | Description                            |
| --------- | -------------------------------------- |
| `match`   | Node `id` if the two faces match.      |
| `noMatch` | Node `id` if the faces do not match.   |
| `error`   | Node `id` if a technical error occurs. |

**Example:**

```json wrap theme={null}
{
  "id": "compare_faces",
  "type": "face_compare",
  "outcomes": {
    "match": "verification_passed",
    "noMatch": "verification_failed",
    "error": "technical_error"
  }
}
```

***

## PERMISSIONS

Requests camera and microphone access from the user's browser. Typically placed early in the flow before any node that requires media access.

**Additional fields:**

| Field      | Type     | Required | Description                    |
| ---------- | -------- | -------- | ------------------------------ |
| `outcomes` | `object` | Yes      | Fixed outcome map (see below). |

**Outcomes:**

| Key       | Description                                             |
| --------- | ------------------------------------------------------- |
| `granted` | Node `id` if the user grants the requested permissions. |
| `denied`  | Node `id` if the user denies permissions.               |

**Example:**

```json wrap theme={null}
{
  "id": "request_permissions",
  "type": "permissions",
  "outcomes": {
    "granted": "start_verification",
    "denied": "permissions_required"
  }
}
```

***

## Flow validation rules

FaceSign validates your flow at session creation time. Flows that violate any of these rules are rejected with an error:

| Rule              | Description                                                                 |
| ----------------- | --------------------------------------------------------------------------- |
| Exactly one START | Every flow must have exactly one node with `type: "start"`.                 |
| At least one END  | Every flow must have at least one node with `type: "end"`.                  |
| No cycles         | The graph must be a directed acyclic graph (DAG). No node can be revisited. |
| Valid references  | Every outcome must point to the `id` of an existing node in the flow.       |
| Complete outcomes | All non-END nodes must define outcomes covering their required keys.        |

## Next steps

<CardGroup cols={2}>
  <Card title="Node Model" href="/docs/reference/concepts#nodes">
    Conceptual overview of how nodes work
  </Card>

  <Card title="Verification Patterns" href="/docs/guides/verification-patterns">
    Production-ready flow patterns for common use cases
  </Card>
</CardGroup>
