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

# Verification question

> Custom verification questions on a search, with structured answers returned in webhooks.

# Verification question

Use verification questions to collect employer or institution responses beyond core verification fields (dates, title, degree, and similar). Define questions on each search when you create an order. Answers are returned on terminal webhooks after the verifier completes the form.

Attach questions via `searchTypes[].verificationQuestions`. See [Search type](/api-reference/schemas/search-type).

<Frame>
  <img src="https://mintcdn.com/thearyai/-r2X9i5G2TIhhphB/assets/diagrams/verification-question-integration-flow.svg?fit=max&auto=format&n=-r2X9i5G2TIhhphB&q=85&s=0194ed0e8f6fdb56e193a059ef9895eb" alt="Integration flow: client creates order with verificationQuestions, API sends form to verifier, verifier returns completed form, API delivers verification.completed webhook with responses" width="1200" height="520" data-path="assets/diagrams/verification-question-integration-flow.svg" />
</Frame>

1. Define custom questions on each relevant `searchTypes[]` entry at order creation.
2. SNH AI sends a form (PDF and/or online questionnaire link) that includes your custom questions and standard employment or education fields.
3. Responses are extracted from the returned form.
4. On `verification.completed`, answers appear in `data.verificationQuestions`. Incomplete or invalid required answers escalate via `verification.action_required` instead.

<Note>
  Questions are defined at **order creation** only. You cannot add or change questions after the order is submitted.
</Note>

## Fields

Send these fields when creating an order. Do **not** send `fieldKey`, `origin`, or `conditionMet` — the platform assigns those.

| Field            | Required    | Type                             | Description                                                                                                                                                      |
| ---------------- | ----------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `questionId`     | Yes         | `integer`                        | Stable ID you assign. Minimum `1`. Must be unique within the search's `verificationQuestions` array.                                                             |
| `type`           | Yes         | enum                             | One of: `free_text`, `single_select`, `multi_select`, `rating_scale`, `yes_no`, `numeric`, `checkbox`.                                                           |
| `prompt`         | Yes         | `string`                         | Question text shown to the verifier. Length 1–500.                                                                                                               |
| `description`    | No          | `string`                         | Helper text under the prompt. Max 500 characters.                                                                                                                |
| `required`       | No          | `boolean`                        | Default `false`. When `true`, the question is required for completion.                                                                                           |
| `options`        | Conditional | `array`                          | Required for `single_select` and `multi_select`. Each item is `{ value, label }` (1–200 characters each). 1–25 options; `value` must be unique within the array. |
| `scale`          | Conditional | `object`                         | Required for `rating_scale`. `{ min, max }` with `0 ≤ min < max ≤ 100`. Optional `minLabel` / `maxLabel`.                                                        |
| `minSelections`  | No          | `integer`                        | `multi_select` only. `1 ≤ minSelections ≤ maxSelections ≤ options.length`.                                                                                       |
| `maxSelections`  | No          | `integer`                        | `multi_select` only. Same bounds as `minSelections`.                                                                                                             |
| `condition`      | No          | `object`                         | One-level dependency. See [Conditional questions](#conditional-questions).                                                                                       |
| `allowComment`   | No          | `boolean`                        | Default `false`. When `true`, the verifier may add a free-text comment.                                                                                          |
| `selectedAnswer` | No          | `string` \| `string[]` \| `null` | Pre-fill. Use `string[]` for `multi_select`; string for other types. Each string ≤ 2000 characters. Default `null`.                                              |
| `comment`        | No          | `string` \| `null`               | Pre-filled comment. ≤ 2000 characters. Default `null`.                                                                                                           |

### Validation limits

| Constraint                                                | Value                                 |
| --------------------------------------------------------- | ------------------------------------- |
| Maximum custom questions per search                       | 50                                    |
| Maximum total questions per search (standard + custom)    | 50                                    |
| `questionId`                                              | Integer ≥ 1, unique in the array      |
| `prompt`                                                  | 1–500 characters                      |
| `description`                                             | ≤ 500 characters                      |
| `options[].value` / `options[].label`                     | 1–200 characters each                 |
| `options` array length (`single_select` / `multi_select`) | 1–25 items, unique `value` per option |
| `selectedAnswer` (each string, including array entries)   | ≤ 2000 characters                     |
| `comment`                                                 | ≤ 2000 characters                     |

Invalid payloads return `400 Bad Request`. See [Errors](/api-reference/errors).

## Question types

| Type            | Use when                        | Verifier input                                   |
| --------------- | ------------------------------- | ------------------------------------------------ |
| `free_text`     | Open-ended text                 | Free text                                        |
| `single_select` | Exactly one choice from a list  | One of `options[].value`                         |
| `multi_select`  | One or more choices from a list | Array of `options[].value`                       |
| `rating_scale`  | Numeric rating in a range       | Integer as string within `scale.min`–`scale.max` |
| `yes_no`        | Binary yes/no                   | `"Yes"` or `"No"`                                |
| `numeric`       | Numeric value                   | Numeric string (for example `"42"`, `"3.5"`)     |
| `checkbox`      | Boolean flag                    | `"true"` or `"false"`                            |

### Type-specific rules

| `type`          | `options`          | `scale`                    | `selectedAnswer` (request and response)        |
| --------------- | ------------------ | -------------------------- | ---------------------------------------------- |
| `free_text`     | Absent             | Absent                     | Any string                                     |
| `single_select` | **Required** (≥ 1) | Absent                     | One of `options[].value`                       |
| `multi_select`  | **Required** (≥ 1) | Absent                     | `string[]` of `options[].value`                |
| `rating_scale`  | Absent             | **Required** (`min < max`) | Stringified integer in range                   |
| `yes_no`        | Absent             | Absent                     | `"Yes"` or `"No"`                              |
| `numeric`       | Absent             | Absent                     | Numeric string `-?\d+(\.\d+)?` when pre-filled |
| `checkbox`      | Absent             | Absent                     | `"true"` or `"false"` when pre-filled          |

<Note>
  For `single_select` and `multi_select`, matching is case-sensitive against `options[].value`. Align webhook parsing with those values. Only set `minSelections` when the question is also `required: true` — otherwise a blank optional `multi_select` can fail validation on PDF, fax, or voice inbound.
</Note>

## Conditional questions

Optional `condition` attaches a one-level dependency on an earlier question:

| Field       | Type                 | Description                                                                                                                                             |
| ----------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `dependsOn` | `integer`            | `questionId` of the parent. Must appear **earlier** in the merged questionnaire (custom array order, or a [standard question](#standard-questions) ID). |
| `operator`  | `"equals"` \| `"in"` | How the parent answer is compared to `values`.                                                                                                          |
| `values`    | `string[]`           | Non-empty. For `equals`, the first value is the expected match.                                                                                         |

**Allowed parent types:** `yes_no`, `single_select`, `checkbox`, `numeric`, `rating_scale` (not `free_text` or `multi_select`).

**Nesting:** One level only — a parent that itself has a `condition` is rejected.

**Semantics after extraction:**

* Parent matches → `conditionMet: true`; `required` applies if set.
* Parent does not match → `conditionMet: false`; child `selectedAnswer` is cleared to `null`.

## Ordering

* **Display order** follows the array order of `verificationQuestions` in the request.
* The resolved form shows **standard questions first**, then custom questions in request order.

## Response fields (webhook)

Each question in webhook payloads includes the request fields plus:

| Field            | Type                             | Description                                                                                                                                       |
| ---------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `fieldKey`       | `string`                         | Machine-readable key. Standard: fixed slug (for example `eligible_for_rehire`). Custom: `q{questionId}_` + slug from prompt (max 127 characters). |
| `origin`         | `"standard"` \| `"custom"`       | Platform default vs your request.                                                                                                                 |
| `selectedAnswer` | `string` \| `string[]` \| `null` | Extracted answer. `null` if unanswered. Arrays for `multi_select` only.                                                                           |
| `comment`        | `string` \| `null`               | Comment when `allowComment` is true.                                                                                                              |
| `conditionMet`   | `boolean`                        | Present when the question has a `condition`.                                                                                                      |

### Where responses appear

| Location                         | Event                          | Contents                                                                          |
| -------------------------------- | ------------------------------ | --------------------------------------------------------------------------------- |
| `data.verificationQuestions`     | `verification.completed`       | Flat array with responses — primary field to consume                              |
| `data.questionnaire`             | `verification.completed`       | Envelope: `schemaVersion`, `searchType`, `questions`                              |
| `metadata.verificationQuestions` | `verification.action_required` | Partial answered set plus subtype metadata when answers are incomplete or invalid |

Prefer `data.verificationQuestions` for most integrations. Use `data.questionnaire` when you need schema version and search type in one object.

### Incomplete or invalid answers

On PDF, fax, and voice inbound paths:

| `metadata.escalationSubtype`   | When                                                                           | Extra metadata                                          |
| ------------------------------ | ------------------------------------------------------------------------------ | ------------------------------------------------------- |
| `MISSING_REQUIRED_INFORMATION` | Applicable required custom question blank                                      | `missingQuestionIds`, `verificationQuestions` (partial) |
| `VERIFICATION_DATA_INVALID`    | Exclusive multi-marks, out-of-option values, or `multi_select` outside min/max | `invalidFields`, `verificationQuestions` (partial)      |

These paths emit `verification.action_required` with `reasonCode: HUMAN_ESCALATION` and do **not** emit `verification.completed`.

## Standard questions

SNH AI prepends standard questions for the search type. Standard `questionId` values use a reserved range (`700001`+). Custom `questionId` values must not collide with a standard ID for that search type — order creation returns `400` if they do. Standard question definitions cannot be overridden.

### Employment (`searchType: "EMPLOYMENT"`)

| questionId | fieldKey              | Type            | Prompt                                    | Required |
| ---------- | --------------------- | --------------- | ----------------------------------------- | -------- |
| 700001     | `eligible_for_rehire` | `single_select` | Eligible for Rehire by your organization? | Yes      |
| 700002     | `reason_for_leaving`  | `single_select` | Reason for Leaving?                       | No       |
| 700003     | `employment_status`   | `single_select` | What was the employment status?           | No       |
| 700004     | `start_date`          | `free_text`     | Start Date                                | No       |
| 700005     | `end_date`            | `free_text`     | End Date                                  | No       |
| 700006     | `position_or_degree`  | `free_text`     | Position                                  | No       |
| 700007     | `salary`              | `free_text`     | Salary                                    | No       |
| 700008     | `additional_comments` | `free_text`     | Additional Comments                       | No       |

| fieldKey              | Allowed `options[].value` values                                                                                 |
| --------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `eligible_for_rehire` | `Yes`, `No`, `No Comment`, `Info N/A`, `Current Employee`                                                        |
| `reason_for_leaving`  | `Terminated`, `Laid Off`, `Voluntary Departure`, `No Comment`, `Info Not Available`, `Current Employee`, `Other` |
| `employment_status`   | `Full-time`, `Part-Time`, `Per Diem`, `Other`, `Not Available`                                                   |

`reason_for_leaving` and `employment_status` have `allowComment: true`.

### Education (`searchType: "EDUCATION"`)

| questionId | fieldKey              | Type        | Prompt              |
| ---------- | --------------------- | ----------- | ------------------- |
| 700101     | `graduated`           | `free_text` | Graduated           |
| 700102     | `graduation_date`     | `free_text` | Graduation Date     |
| 700103     | `start_date`          | `free_text` | Start Date          |
| 700104     | `end_date`            | `free_text` | End Date            |
| 700105     | `position_or_degree`  | `free_text` | Degree              |
| 700106     | `major_or_department` | `free_text` | Major               |
| 700107     | `gpa`                 | `free_text` | GPA                 |
| 700108     | `honors`              | `free_text` | Honors              |
| 700109     | `additional_comments` | `free_text` | Additional Comments |

All education standard questions are optional (`required: false`) with `allowComment: false`.

## Questionnaire envelope

When `data.questionnaire` is present:

| Field           | Type                     | Description                              |
| --------------- | ------------------------ | ---------------------------------------- |
| `schemaVersion` | `string`                 | `"1.1"`; `"1.0"` remains readable        |
| `searchType`    | enum                     | `"EMPLOYMENT"` or `"EDUCATION"`          |
| `questions`     | `VerificationQuestion[]` | Ordered standard + custom with responses |

## Example

### Custom questions on order creation

```json theme={null}
{
  "searchTypes": [
    {
      "searchType": "EMPLOYMENT",
      "externalSearchId": "emp-2026-001",
      "questionnaireUrl": "https://verify.example.com/form/abc123",
      "verificationQuestions": [
        {
          "questionId": 100001,
          "type": "single_select",
          "prompt": "Would you rehire this candidate for a similar role?",
          "required": true,
          "options": [
            { "value": "Yes", "label": "Yes" },
            { "value": "No", "label": "No" },
            { "value": "No Comment", "label": "No Comment" }
          ]
        },
        {
          "questionId": 100002,
          "type": "multi_select",
          "prompt": "Which skills did the candidate demonstrate?",
          "required": true,
          "minSelections": 1,
          "maxSelections": 3,
          "options": [
            { "value": "leadership", "label": "Leadership" },
            { "value": "communication", "label": "Communication" },
            { "value": "technical", "label": "Technical" }
          ]
        },
        {
          "questionId": 100003,
          "type": "free_text",
          "prompt": "Please explain why they are not eligible for rehire.",
          "required": true,
          "condition": {
            "dependsOn": 700001,
            "operator": "equals",
            "values": ["No"]
          }
        }
      ]
    }
  ]
}
```

In the example above, `condition.dependsOn: 700001` targets the standard employment `eligible_for_rehire` question.

### Webhook response (`verification.completed`)

```json theme={null}
{
  "event": "verification.completed",
  "occurredAt": "2026-06-15T14:30:00.000Z",
  "data": {
    "searchId": "123e4567-e89b-12d3-a456-426614174000",
    "externalSearchId": "emp-2026-001",
    "verificationQuestions": [
      {
        "questionId": 700001,
        "fieldKey": "eligible_for_rehire",
        "origin": "standard",
        "type": "single_select",
        "prompt": "Eligible for Rehire by your organization?",
        "required": true,
        "options": [
          { "value": "Yes", "label": "Yes" },
          { "value": "No", "label": "No" },
          { "value": "No Comment", "label": "No Comment" },
          { "value": "Info N/A", "label": "Info N/A" },
          { "value": "Current Employee", "label": "Current Employee" }
        ],
        "allowComment": false,
        "selectedAnswer": "Yes",
        "comment": null
      },
      {
        "questionId": 100002,
        "fieldKey": "q100002_which_skills_did_the_candidate_demonstrate",
        "origin": "custom",
        "type": "multi_select",
        "prompt": "Which skills did the candidate demonstrate?",
        "required": true,
        "options": [
          { "value": "leadership", "label": "Leadership" },
          { "value": "communication", "label": "Communication" },
          { "value": "technical", "label": "Technical" }
        ],
        "minSelections": 1,
        "maxSelections": 3,
        "allowComment": false,
        "selectedAnswer": ["leadership", "communication"],
        "comment": null
      }
    ],
    "questionnaire": {
      "schemaVersion": "1.1",
      "searchType": "EMPLOYMENT",
      "questions": []
    }
  }
}
```

<Note>
  `questionnaire.questions` is abbreviated as `[]` above. In production it matches `data.verificationQuestions`.
</Note>

### Escalation (`verification.action_required`)

```json theme={null}
{
  "event": "verification.action_required",
  "occurredAt": "2026-06-15T14:30:00.000Z",
  "data": {
    "searchId": "123e4567-e89b-12d3-a456-426614174000",
    "externalSearchId": "emp-2026-001",
    "reasonCode": "HUMAN_ESCALATION",
    "status": "QA Review",
    "metadata": {
      "source": "inbound",
      "escalationSubtype": "MISSING_REQUIRED_INFORMATION",
      "missingQuestionIds": [100001],
      "verificationQuestions": [
        {
          "questionId": 100001,
          "fieldKey": "q100001_would_you_rehire_this_candidate_for_a_similar_role",
          "origin": "custom",
          "type": "single_select",
          "prompt": "Would you rehire this candidate for a similar role?",
          "required": true,
          "selectedAnswer": null,
          "comment": null
        }
      ]
    }
  }
}
```

## Validation rules

* **Stable IDs** — Reuse the same `questionId` across orders for correlation. Custom `fieldKey` is derived from the prompt; changing the prompt changes `fieldKey`.
* **Unique `questionId`** — Must be unique within each `verificationQuestions` array and must not collide with standard IDs (`700001`+).
* **Pre-fill** — Set `selectedAnswer` / `comment` at order creation when known. The verifier may still change values on the form.
* **Parsing** — Index by `fieldKey` for standard questions and by `questionId` or `fieldKey` for custom. Use `origin` to distinguish them. Treat `multi_select` answers as arrays.
* **`multi_select` answers** — `selectedAnswer` is a `string[]` for this type only. Other types remain strings (or `null`).

## Related schemas

* [Search type](/api-reference/schemas/search-type) — Where `verificationQuestions` attaches to each search
* [Create order](/api-reference/endpoints/create-order) — Full request body reference
* [Webhook events](/webhooks/events) — `verification.completed` and `verification.action_required`
* [Webhook integration](/webhooks/integration) — Delivery, signatures, and retries
