> ## 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.

# Submit Inbound Notification

> Submit verification responses from employers, schools, and third-party services

# Submit Inbound Notification

Use this endpoint to submit verification responses from external parties. The system processes responses through various channels (email, forms, fax, and generic JSON) and automatically extracts verification details using AI.

## Authentication

**Bearer Token Required** - Include your JWT token in the Authorization header.

## Quick Examples

```bash theme={null}
# For employment verification
curl -sS -X POST "https://sandbox.theary.ai/background-check/v1/inbound/notifications" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "messageId": "msg_001",
    "threadId": "thread_001",
    "channel": "EMAIL",
    "messageData": {
      "id": "msg_001",
      "threadId": "thread_001",
      "from": "hr@company.com",
      "to": "verifications@theary.ai",
      "subject": "Employment Verification - John Doe",
      "body": {
        "text": "John Doe worked here from 2020-01-15 to 2023-12-31 as Software Engineer."
      },
      "timestamp": "2026-01-15T10:30:00.000Z"
    }
  }'

# For education verification
curl -sS -X POST "https://sandbox.theary.ai/background-check/v1/inbound/notifications" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "messageId": "msg_001",
    "threadId": "thread_001",
    "channel": "EMAIL",
    "messageData": {
      "id": "msg_001",
      "threadId": "thread_001",
      "from": "capelookouthighschool@edu.com",
      "to": "verifications@theary.ai",
      "subject": "Education Verification - John Doe",
      "body": {
        "text": "John Doe completed with a Highschool Diploma at Cape Lookout High School in Moorehead City NC with graduation date as 2023-05-15"
      },
      "timestamp": "2024-01-15T10:30:00.000Z"
    }
  }'
```

## Request Parameters

### Optional Fields

<ParamField body="messageId" type="string">
  Unique identifier for this message. If not provided, the system will auto-generate one using the format `msg_<timestamp>_<random>`. You can also provide it in `messageData.id` - the system will use the top-level `messageId` if present, otherwise `messageData.id`, otherwise auto-generate.
</ParamField>

<ParamField body="threadId" type="string">
  Thread identifier to link related messages. If not provided, the system will use the `messageId` as the `threadId`. You can also
  provide it in `messageData.threadId` - the system will use the top-level `threadId` if present, otherwise
  `messageData.threadId`, otherwise use the `messageId`.
</ParamField>

<ParamField body="messageData" type="object" required>
  Message content and metadata

  <Expandable title="properties">
    <ParamField body="id" type="string">
      Message ID (optional - will be auto-generated if not provided and matches top-level messageId)
    </ParamField>

    <ParamField body="threadId" type="string">
      Thread ID (optional - will be auto-generated if not provided and matches top-level threadId)
    </ParamField>

    <ParamField body="from" type="string">
      Sender email address. Required for non-JSON channels; optional for `JSON` channel requests.
    </ParamField>

    <ParamField body="to" type="string">
      Recipient email address (e.g., [verifications@theary.ai](mailto:verifications@theary.ai)). Required for non-JSON channels; optional for `JSON` channel requests.
    </ParamField>

    <ParamField body="timestamp" type="string" required>
      ISO 8601 timestamp (e.g., 2026-01-15T10:30:00.000Z)
    </ParamField>

    <ParamField body="subject" type="string">
      Email subject line
    </ParamField>

    <ParamField body="body" type="object">
      Message content with `text` and/or `html` fields. Used by email, fax, form, and ticketing channels. Not used for `JSON`
      channel extraction (use `data` instead).
    </ParamField>

    <ParamField body="data" type="object">
      Structured third-party provider payload for `JSON` channel requests. **Required when `channel` is `JSON`.** Pass the vendor
      response object as-is. The API enumerates employment or education history records from common vendor shapes (see [Supported
      JSON payload shapes](#supported-json-payload-shapes)). An empty object fails with `No JSON payload found`.
    </ParamField>

    <ParamField body="attachments" type="array">
      File attachments (PDFs are supported and will be processed for text extraction). Each attachment should have: - `id`
      (string) - Unique attachment identifier - `name` (string) - Attachment filename - `contentType` (string) - MIME type (e.g.,
      "application/pdf") - `size` (number) - File size in bytes - `content` (string, optional) - Base64-encoded file content
      (required for PDF processing)
    </ParamField>
  </Expandable>
</ParamField>

### Optional Fields

<ParamField body="channel" type="string">
  Communication channel (defaults to `STANDARD`) <br />
  **Available Values:** `STANDARD`, `EMAIL`, `EFORM`, `MICROSOFT_GRAPH`, `FAX_WEBHOOK`, `VOICE_TRANSCRIPT`, `TICKETING`, `JITBIT`, `JSON` <br />
  **Channel Types:** <br />

  * `STANDARD` - Default when `channel` is omitted <br />
  * `EMAIL` - Email responses <br />
  * `EFORM` - Electronic forms <br />
  * `MICROSOFT_GRAPH` - Office 365 <br />
  * `FAX_WEBHOOK` - Fax responses <br />
  * `VOICE_TRANSCRIPT` - Phone/voice transcription responses <br />
  * `TICKETING` / `JITBIT` - Ticketing systems <br />
  * `JSON` - Generic structured JSON payloads from upstream systems <br />
</ParamField>

<ParamField body="metadata" type="object">
  Additional context data. Required fields depend on channel.

  <Expandable title="properties">
    <ParamField body="searchRefId" type="string">
      **Required for `JSON` channel.** Identifies the verification search to update. Matched against the search's
      `externalSearchId` or internal reference number. For JSON requests, the API uses this value exclusively and does not scan
      the payload for reference numbers in the message body.
    </ParamField>

    <ParamField body="provider" type="string">
      Source system identifier (recommended for `JSON` channel traceability, e.g. `equifax`, `thomas-and-company`).
    </ParamField>

    <ParamField body="structuredData" type="object">
      Pre-extracted verification data from upstream systems (non-JSON channels). When `verifiedInfo` is present, the API may skip
      re-extraction. See the OpenAPI schema for `verificationResult` values.
    </ParamField>
  </Expandable>
</ParamField>

### Attachments

Attachments are supported for PDF files. PDFs are automatically processed to extract text content, which is then used during message classification and verification data extraction.

**Supported Format:**

* **PDF files** (`application/pdf`) - Fully supported with text extraction
* Other file types may be included but will not be processed

**Attachment Processing:**

* PDFs are processed using document extraction tools
* Extracted text is included in the message analysis
* Processing errors are logged but do not block message processing

## Response

<ResponseField name="messageId" type="string">
  Echo of the submitted message ID
</ResponseField>

<ResponseField name="success" type="boolean">
  Whether processing succeeded
</ResponseField>

<ResponseField name="classification" type="object">
  AI-powered message classification <br />
  **Fields:** <br />

  * `type` - Classification type (see Classification Types below) <br />
  * `confidence` - Score from 0 to 1 indicating classification confidence <br />- `reasoning` - Explanation of the classification
    decision
</ResponseField>

<ResponseField name="employmentVerification" type="object">
  Root-level highest-confidence normalized employment verification object, omitted from `extractedData`. This temporary
  backward-compatible shape applies to every channel. Each object includes
  backward-compatible flat `provided*` / `verified*` values (including applicant names and `providedReasonLeft`) and the canonical `fields` comparison map. See the
  [EmploymentVerification object](/webhooks/events#employmentverification-object) for field definitions.
</ResponseField>

<ResponseField name="educationVerification" type="object">
  Education counterpart to root-level `employmentVerification`, omitted from `extractedData`. This is the single
  highest-confidence provider education-history object for every channel. Each object includes backward-compatible flat
  `provided*` / `verified*` values and the canonical `fields` comparison map. See the [EducationVerification
  object](/webhooks/events#educationverification-object) for field definitions.
</ResponseField>

<Note>
  `employmentVerification` and `educationVerification` temporarily return one object for backward compatibility. A future
  version will return sorted arrays after consumers migrate to the array contract.
</Note>

<ResponseField name="extractedData" type="object">
  Structured verification data extracted from the message. Present on successful processing for every channel, including `JSON`.
  Does **not** include duplicate `employmentVerification` / `educationVerification` objects when those are returned at the response
  root.

  <Expandable title="common fields">
    <ResponseField name="outcome" type="string">
      Normalized verification outcome (e.g. `verified`, `no_record`, `third_party`).
    </ResponseField>

    <ResponseField name="employerName" type="string">
      Extracted employer name (employment searches). Best-match scalar for JSON channel; same source as `employmentVerification`.
    </ResponseField>

    <ResponseField name="jobTitle" type="string">
      Extracted job title (employment searches).
    </ResponseField>

    <ResponseField name="employerLocation" type="string">
      Extracted employer location when present on the selected record.
    </ResponseField>

    <ResponseField name="extractedApplicantName" type="string">
      Applicant name from the selected verification record.
    </ResponseField>

    <ResponseField name="verifiedDates" type="object">
      Structured date mirror (`startDate`, `endDate`, etc.) for backward compatibility.
    </ResponseField>

    <ResponseField name="verifiedContent" type="object">
      Structured content mirror (for example `positionTitle`) for backward compatibility.
    </ResponseField>

    <ResponseField name="extraction" type="object">
      Optional duplicate of scalar extraction fields for backward compatibility.
    </ResponseField>

    <ResponseField name="applicantMismatch" type="boolean">
      **`JSON` channel.** `true` when a critical mismatch is detected on the best-match history row under tenant comparison settings. Field-level comparison details are on root-level `employmentVerification.fields` / `educationVerification.fields`, not in `extractedData`.

      **`applicantMismatches` and `applicantDiscrepancies` are not returned** in webhook or inbound notification payloads (removed). Use `fields.*.status`, `expected`, `extracted`, and `comparison` on the verification object instead.
    </ResponseField>

    <ResponseField name="_requiresManualReview" type="boolean">
      **`JSON` channel.** Present when a critical mismatch on the best-match record triggers manual review routing
      (`applicantMismatch: true`). May appear in both the synchronous response and downstream inbound-notification or
      action-required webhooks.
    </ResponseField>

    <ResponseField name="_processedFromJson" type="boolean">
      **`JSON` channel.** `true` when `extractedData` was produced from a structured third-party JSON payload rather than
      email/fax/voice extraction. Present in the synchronous response and downstream payloads.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="failureReasons" type="array">
  Error messages if failed (empty array if successful)
</ResponseField>

<ResponseField name="metadata" type="object">
  Processing details including `searchId`, `outcome`, and `channel`
</ResponseField>

<ResponseField name="processedAt" type="string">
  Processing completion timestamp (ISO 8601 format)
</ResponseField>

### Classification Types

The classification `type` field is one of the `INTENT` enum values assigned by the inbound processing pipeline:

* `VERIFICATION` - Verification confirmed with details
* `INFO_REQUEST` - Employer/school needs more information, or a general information request
* `INFO_PROVIDED` - Applicant provided information that may need follow-up
* `NEXT_CONTACT` - Indicates a different contact at the entity should be reached next
* `WRONG_ENTITY` - Message indicates the wrong organization/entity was contacted
* `RECORD_NOT_FOUND` - No employment/education record found
* `AUTHORIZATION_FORM_REQUEST` - Authorization form is needed before the entity will respond
* `NO_ACTION_REQUIRED` - Message requires no further action
* `UNKNOWN` - Could not determine intent
* `OTHER` - Other classification that doesn't fit the above

<Note>
  `THIRD_PARTY` is a deprecated legacy value — the classifier remaps it to `WRONG_ENTITY` internally, so it will never appear in a response's `classification.type`.
</Note>

## Examples

### Employment Verification

<CodeGroup>
  ```bash cURL theme={null}
  curl -sS -X POST "https://sandbox.theary.ai/background-check/v1/inbound/notifications" \
    -H "Authorization: Bearer YOUR_JWT_TOKEN" \
    -H "Content-Type: application/json" \
    -H "Accept: application/json" \
    -d '{
      "messageId": "emp_001",
      "threadId": "thread_abc",
      "channel": "EMAIL",
      "messageData": {
        "id": "emp_001",
        "threadId": "thread_abc",
        "from": "hr@techcorp.com",
        "to": "verifications@theary.ai",
        "subject": "Re: Employment Verification - John Doe",
        "body": {
          "text": "Reference: 11111111\n\nJohn Doe was employed as Software Engineer from 2020-01-15 to 2023-12-31. Employee ID: EMP001."
        },
        "timestamp": "2026-01-15T10:30:00.000Z"
      }
    }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch('https://sandbox.theary.ai/background-check/v1/inbound/notifications', {
    method: 'POST',
    headers: {
      Authorization: 'Bearer YOUR_JWT_TOKEN',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      messageId: 'emp_001',
      threadId: 'thread_abc',
      channel: 'EMAIL',
      messageData: {
        id: 'emp_001',
        threadId: 'thread_abc',
        from: 'hr@techcorp.com',
        to: 'verifications@theary.ai',
        subject: 'Re: Employment Verification - John Doe',
        body: {
          text: 'John Doe was employed as Software Engineer from 2020-01-15 to 2023-12-31. Employee ID: EMP001.',
        },
        timestamp: '2026-01-15T10:30:00.000Z',
      },
    }),
  })

  const result = await response.json()
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      'https://sandbox.theary.ai/background-check/v1/inbound/notifications',
      headers={
          'Authorization': 'Bearer YOUR_JWT_TOKEN',
          'Content-Type': 'application/json'
      },
      json={
          "messageId": "emp_001",
          "threadId": "thread_abc",
          "channel": "EMAIL",
          "messageData": {
              "id": "emp_001",
              "threadId": "thread_abc",
              "from": "hr@techcorp.com",
              "to": "verifications@theary.ai",
              "subject": "Re: Employment Verification - John Doe",
              "body": {
                  "text": "Reference: 11111111\n\nJohn Doe was employed as Software Engineer from 2020-01-15 to 2023-12-31. Employee ID: EMP001."
              },
              "timestamp": "2026-01-15T10:30:00.000Z"
          }
      }
  )

  result = response.json()
  ```
</CodeGroup>

**Success Response (non-JSON channels):**

Non-JSON successful submissions return a single root-level `employmentVerification` object with per-field comparison in `fields`
(not nested in `extractedData`). Example from an EMAIL channel response for Pablo's Pizza / John Smith:

```json theme={null}
{
  "messageId": "emp_001",
  "success": true,
  "classification": {
    "type": "VERIFICATION",
    "confidence": 0.85,
    "reasoning": "Entity verification"
  },
  "extractedData": {
    "outcome": "verified",
    "employerName": "Pablo's Pizza",
    "jobTitle": "Senior Software Developer",
    "startDate": "2022-01-01",
    "endDate": "2023-01-01",
    "extractedApplicantName": "John Smith",
    "verifiedDates": {
      "startDate": "2022-01-01",
      "endDate": "2023-01-01"
    },
    "verifiedContent": {
      "positionTitle": "Senior Software Developer"
    },
    "refId": "EXT-DEV-1781045734449-1",
    "searchType": "EMPLOYMENT"
  },
  "employmentVerification": {
      "fields": {
        "companyName": {
          "type": "string",
          "status": "match",
          "expected": "Pablo's Pizza",
          "extracted": "Pablo's Pizza",
          "comparison": { "engine": "deterministic", "similarity": 1 }
        },
        "position": {
          "type": "string",
          "status": "match",
          "expected": "Senior Software Developer",
          "extracted": "Senior Software Developer",
          "comparison": { "engine": "deterministic", "similarity": 1 }
        },
        "startDate": {
          "type": "date",
          "status": "match",
          "expected": "2022-01-01",
          "extracted": "2022-01-01",
          "comparison": { "engine": "deterministic", "distance": 0 }
        },
        "endDate": {
          "type": "date",
          "status": "match",
          "expected": "2023-01-01",
          "extracted": "2023-01-01",
          "comparison": { "engine": "deterministic", "distance": 0 }
        },
        "applicantName": {
          "type": "string",
          "status": "match",
          "expected": "John Smith",
          "extracted": "John Smith",
          "comparison": { "engine": "deterministic", "similarity": 1 }
        },
        "salary": {
          "type": "number",
          "status": "match",
          "expected": "100000",
          "extracted": "100000",
          "comparison": { "engine": "deterministic", "distance": 0 }
        },
        "location": {
          "type": "string",
          "status": "not_checked",
          "expected": null,
          "extracted": "Grand Junction, CO",
          "comparison": { "engine": "none" }
        },
        "contactNumber": {
          "type": "string",
          "status": "not_checked",
          "expected": null,
          "extracted": "+1-555-200-1000",
          "comparison": { "engine": "none" }
        }
      },
      "doNotContact": false,
      "comments": "Email confirms employment at Pablo's Pizza for John Smith in the stated role and dates."
  },
  "extractedData": {
    "employmentVerified": true,
    "position": "Software Engineer",
    "startDate": "2020-01-15",
    "endDate": "2023-12-31"
  },
  "failureReasons": [],
  "metadata": {
    "contactAnalysis": "ENTITY",
    "searchId": "ae98cdb9-e7b9-4a44-b4e2-d4aa565a2f66",
    "referenceNumbers": ["EXT-DEV-1781045734449-1"],
    "channel": "EMAIL",
    "outcome": "verified"
  },
  "processedAt": "2026-06-09T23:42:32.105Z"
}
```

### Education Form Submission

<CodeGroup>
  ```bash cURL theme={null}
  curl -sS -X POST "https://sandbox.theary.ai/background-check/v1/inbound/notifications" \
    -H "Authorization: Bearer YOUR_JWT_TOKEN" \
    -H "Content-Type: application/json" \
    -H "Accept: application/json" \
    -d '{
      "messageId": "edu_001",
      "threadId": "thread_xyz",
      "channel": "EFORM",
      "messageData": {
        "id": "edu_001",
        "threadId": "thread_xyz",
        "from": "registrar@university.edu",
        "to": "verifications@theary.ai",
        "subject": "Education Verification - Jane Smith",
        "body": {
          "text": "Reference: 22222222\n\nStudent: Jane Smith\nDegree: BS in Computer Science\nGraduation: 2019-05-15\nGPA: 3.8\nTranscript attached separately via email."
        },
        "timestamp": "2026-01-15T11:45:00.000Z"
      },
      "metadata": {
        "formType": "education_verification"
      }
    }'
  ```
</CodeGroup>

**Success Response:**

```json theme={null}
{
  "messageId": "edu_001",
  "success": true,
  "classification": {
    "type": "VERIFICATION",
    "confidence": 0.95,
    "reasoning": "Electronic form with structured education verification data"
  },
  "metadata": {
    "searchId": "789e0123-e89b-12d3-a456-426614174002",
    "outcome": "verified"
  },
  "processedAt": "2026-01-15T11:45:03.000Z"
}
```

### Generic JSON Submission

Use `channel: "JSON"` when an upstream system sends structured third-party verification data instead of an email, fax, or ticket transcript. Place the provider payload in `messageData.data`.

**JSON channel requirements:**

| Field                                | Required | Notes                                                         |
| ------------------------------------ | -------- | ------------------------------------------------------------- |
| `channel`                            | Yes      | Must be `"JSON"`                                              |
| `messageData.timestamp`              | Yes      | ISO 8601 timestamp                                            |
| `messageData.data`                   | Yes      | Non-empty provider payload object                             |
| `metadata.searchRefId`               | Yes      | Search identifier (`externalSearchId` or internal reference)  |
| `messageId`, `threadId`              | No       | Auto-generated when omitted                                   |
| `messageData.from`, `messageData.to` | No       | Defaults to `metadata.provider` and `"JSON"` for traceability |

JSON payloads may contain the full employment or education history returned by the provider. When multiple records are present, SNH AI includes the matched search's applicant and submitted history context in extraction so the most relevant record can be identified, extracted, and compared to the submitted applicant data.

#### Supported JSON payload shapes

The API detects employment or education history records from common vendor response layouts. You do not need to normalize the payload to a single schema — pass the vendor object in `messageData.data`.

**Employment searches** — records are enumerated from:

| Path                                                                                                                    | Typical vendor                                                     |
| ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| `employmentData[]`                                                                                                      | Equifax Talent Report Employment Select All (`TrEsaResponse.data`) |
| `data.employmentData[]`                                                                                                 | Wrapped Equifax employment responses                               |
| `Data[]`                                                                                                                | Thomas & Company Employment Response V2                            |
| Other arrays at depth ≤ 2 containing `employer`/`employee`, `Employer`/`Employee`, `Positions`, or `WorkHistories` keys | Fallback detection                                                 |

**Education searches** — records are enumerated from:

| Path                                                                                                   | Typical vendor                                  |
| ------------------------------------------------------------------------------------------------------ | ----------------------------------------------- |
| `educationVerification.degreeVerification[]`                                                           | Equifax Talent Report Education                 |
| `educationVerification.enrollmentVerification[]`                                                       | Equifax Dates of Attendance                     |
| `...degreeVerification[].degreeDetails.degreeDetail[]`                                                 | Nested degree records                           |
| `...degreeVerification[].enrollmentDetails.enrollmentDetail[]`                                         | Nested enrollment under degree verification     |
| `...enrollmentVerification[].enrollmentDetails.enrollmentDetail[]`                                     | Nested enrollment under enrollment verification |
| Other arrays at depth ≤ 2 containing `degreeTitle`, `officialNameOfSchool`, `nameOnSchoolRecord`, etc. | Fallback detection                              |

#### Employment JSON example

<CodeGroup>
  ```bash cURL theme={null}
  curl -sS -X POST "https://sandbox.theary.ai/background-check/v1/inbound/notifications" \
    -H "Authorization: Bearer YOUR_JWT_TOKEN" \
    -H "Content-Type: application/json" \
    -H "Accept: application/json" \
    -d '{
      "channel": "JSON",
      "messageData": {
        "data": {
          "employmentData": [
            {
              "employer": { "name": "Pablos Pizza" },
              "employee": {
                "firstName": "John",
                "lastName": "Smith",
                "positionTitle": "Senior Software Engineer",
                "mostRecentHireDate": "2022-01-01",
                "terminationDate": "2023-01-01",
                "workLocation": {
                  "address": { "city": "Grand Junction", "state": "CO" }
                }
              }
            },
            {
              "employer": { "name": "SNH AI" },
              "employee": {
                "firstName": "John",
                "lastName": "Smith",
                "positionTitle": "Software Engineer",
                "mostRecentHireDate": "2020-01-01",
                "terminationDate": "2023-01-01",
                "workLocation": {
                  "address": { "city": "Austin", "state": "TX" }
                }
              }
            },
            {
              "employer": { "name": "Other Corp" },
              "employee": {
                "firstName": "John",
                "lastName": "Smith",
                "positionTitle": "Software Intern",
                "mostRecentHireDate": "2019-01-01",
                "terminationDate": "2020-01-01",
                "workLocation": {
                  "address": { "city": "Denver", "state": "CO" }
                }
              }
            }
          ]
        },
        "timestamp": "2026-01-15T12:00:00.000Z"
      },
      "metadata": {
        "provider": "test-provider",
        "searchRefId": "EXT-DEV-1781042158637-1"
      }
    }'
  ```
</CodeGroup>

**Employment success response:** HTTP 200 with the standard processing response body. `extractedData` contains the best-match
scalars and operational flags, while root-level `employmentVerification` contains the single highest-confidence provider history
object.

#### Education JSON example

<CodeGroup>
  ```bash cURL theme={null}
  curl -sS -X POST "https://sandbox.theary.ai/background-check/v1/inbound/notifications" \
    -H "Authorization: Bearer YOUR_JWT_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "channel": "JSON",
      "messageData": {
        "data": {
          "educationVerification": {
            "degreeVerification": [
              {
                "degreeDetails": {
                  "degreeDetail": [
                    {
                      "officialNameOfSchool": "Cape Lookout High School",
                      "degreeTitle": "High School Diploma",
                      "awardDate": "2023-05-15",
                      "nameOnSchoolRecord": {
                        "firstName": "John",
                        "lastName": "Doe"
                      }
                    }
                  ]
                }
              }
            ]
          }
        },
        "timestamp": "2024-01-15T12:00:00.000Z"
      },
      "metadata": {
        "provider": "equifax",
        "searchRefId": "EXT-EDU-12345678"
      }
    }'
  ```
</CodeGroup>

Successful `channel: "JSON"` submissions return the same synchronous processing envelope as other channels: `messageId`, `success`,
`classification`, `extractedData`, the applicable root-level `employmentVerification` / `educationVerification` object, `metadata`,
and `processedAt`. The verification object is the highest-confidence provider history row. The employment example selects the row
with confidence `0.94` for search `EXT-DEV-1781042158637-1`. The raw
provider payload is not echoed in the synchronous response or webhook payloads. For the full `verification.completed` JSON closeout
shape, see [Webhook events — Employment Verification (JSON
channel)](/webhooks/events#example-employment-verification-json-channel).

#### Comparison fields: selected JSON record

JSON channel responses expose comparison output at two levels:

| Location                                                                                                                                                                                    | Purpose                                                                                                                                                                                                                                                                                                   |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Root-level `employmentVerification` / `educationVerification` (sync API response, `verification.notification`, activity metadata, `verification.action_required`, `verification.completed`) | **Canonical selected verification** — the highest-confidence normalized object with per-field `fields.*.comparison` metadata. Omitted from `extractedData` / `metadata.extractedData`. **All externally returned field-level comparison (matches, mismatches, and `match_with_discrepancy`) lives here.** |
| `extractedData.applicantMismatch`, `_requiresManualReview`                                                                                                                                  | **Operational flags (best-match row only)** — boolean escalation signals when a critical mismatch triggers manual review. Does **not** include field-level detail.                                                                                                                                        |
| `employmentVerification.fields` / `educationVerification.fields`                                                                                                                            | **Selected-row detail** — comparison detail for the highest-confidence record. Replaces the removed `applicantMismatches` / `applicantDiscrepancies` objects.                                                                                                                                             |

#### Field comparison metadata (`fields.*.comparison`)

Normalized verification objects (for example `employmentVerification.fields` on downstream webhooks) attach per-field comparison metadata. **`similarity` is not a universal score** — deterministic exact string matches use `similarity: 1`, and probabilistic / organization-name comparisons use their evaluated score. Date and number fields never include `similarity`; deterministic matches and mismatches on those types use `distance` instead.

| Field type                          | Example fields                                             | Match (`status: "match"`)                                                                                                                         | Mismatch (`status: "mismatch"`)                                                                                                                              | Discrepancy (`status: "match_with_discrepancy"`)                                                                                                         |
| ----------------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `date`                              | `startDate`, `endDate`, `graduationDate`                   | `comparison.engine`: `deterministic`. `comparison.distance`: calendar days between expected and extracted (`0` for exact dates). No `similarity`. | `comparison.engine`: `deterministic`. `comparison.distance`: calendar days between expected and extracted. No `similarity`.                                  | Not used for dates.                                                                                                                                      |
| `number`                            | `salary`, `gpa`                                            | `comparison.engine`: `deterministic`. `comparison.distance`: absolute difference (`0` for exact numbers). No `similarity`.                        | `comparison.engine`: `deterministic`. `comparison.distance`: absolute difference (for salary, normalized annual USD when both sides parse). No `similarity`. | Not used for numbers.                                                                                                                                    |
| `string` (LLM semantic / proximity) | `applicantName`, `position`, `degree`, `major`, `location` | Exact normalized match: `comparison.engine`: `deterministic`. `comparison.similarity`: `1`.                                                       | `comparison.engine`: `probabilistic` when LLM evaluates the pair. `comparison.similarity`: LLM score (0–1); falls back to `0` when no score is available.    | `comparison.engine`: `probabilistic`. `comparison.similarity`: LLM score (0–1).                                                                          |
| `string` (organization name)        | `companyName`, `schoolName`                                | Exact normalized match: `comparison.engine`: `deterministic`. `comparison.similarity`: `1`.                                                       | May use `probabilistic` LLM fallback or deterministic organization scoring with `similarity`; deterministic mismatches without an evaluated score use `0`.   | Typo-tolerant match: `comparison.engine`: `deterministic` or `probabilistic`. May include `comparison.similarity` from string-similarity or LLM scoring. |

**Examples**

Location proximity mismatch:

```json theme={null}
"location": {
  "type": "string",
  "status": "mismatch",
  "expected": "Grand Junction, CO",
  "extracted": "Austin, TX",
  "reason": "The locations are in different states and are not geographically close.",
  "comparison": { "engine": "probabilistic", "similarity": 0.1 }
}
```

Location proximity discrepancy:

```json theme={null}
"location": {
  "type": "string",
  "status": "match_with_discrepancy",
  "expected": "Phoenix, AZ",
  "extracted": "Scottsdale, AZ",
  "reason": "The locations are highly similar because Scottsdale is in the Phoenix metro area.",
  "comparison": { "engine": "probabilistic", "similarity": 0.95 }
}
```

Deterministic date match (`distance: 0`, no `similarity`):

```json theme={null}
"startDate": {
  "type": "date",
  "status": "match",
  "expected": "2022-01-01",
  "extracted": "2022-01-01",
  "reason": "exact match",
  "comparison": { "engine": "deterministic", "distance": 0 }
}
```

LLM semantic discrepancy (`similarity` present):

```json theme={null}
"position": {
  "type": "string",
  "status": "match_with_discrepancy",
  "expected": "Senior Software Developer",
  "extracted": "Senior Software Engineer",
  "reason": "Software Developer and Software Engineer describe the same engineering function.",
  "comparison": { "engine": "probabilistic", "similarity": 0.98 }
}
```

#### Downstream webhook delivery

In addition to the synchronous response body, successful JSON submissions emit extraction and the selected normalized verification object
asynchronously. Primary delivery paths:

* **`verification.notification`** with `notificationType: "INBOUND_RECEIVED_DETAIL"` — best-match scalars (and optional `applicantMismatch` / `_requiresManualReview` flags) at `data.metadata.extractedData`; the highest-confidence record with per-field comparison at root-level `data.employmentVerification` or `data.educationVerification` (not nested in `extractedData`). **`applicantMismatches` and `applicantDiscrepancies` are not included.**
* **`verification.action_required`** with `reasonCode: "HUMAN_ESCALATION"` or `"OTHER"` — when manual review is required for **non-verified** or JSON-channel holds; `extractedData` under `data.metadata.extractedData` without duplicate verification data. **JSON-channel only (`channel: "JSON"`, `status: "QA Review"`):** root-level `data.message` (human-readable review summary for client UI) plus the highest-confidence `data.employmentVerification` or `data.educationVerification` object. **EMAIL/FAX/VOICE action-required webhooks omit `message`.** Verified EMAIL/FAX/VOICE with field mismatches typically close via **`verification.completed`** and `verificationResult.discrepancy: true` instead.
* **Activity metadata** — root-level `employmentVerification` / `educationVerification` plus stripped `extractedData` (same split as webhooks), alongside `messageId`, `threadId`, `contactType`, and `messageContext`
* **`verification.completed`** — root-level `data.employmentVerification` (or `data.educationVerification`) as the single highest-confidence **object** for JSON. `data.verificationResult.extractedData` keeps scalar extraction and comparison metadata without duplicate verification data. `verificationResult.discrepancy` is the operational closeout flag and may be `false` when the selected row only has non-critical `fields.*.status: "match_with_discrepancy"` entries.

The examples below show the **`INBOUND_RECEIVED_DETAIL`** webhook envelope for search `86bc0a43-6a10-4f85-a093-e4244ed78107` / `EXT-DEV-1781042158637-1` (Pablo's Pizza / John Smith). `metadata.extractedData` is identical wherever that object appears; the selected verification object sits at the payload root.

<CodeGroup>
  ```json Critical mismatch (manual review) theme={null}
  {
    "event": "verification.notification",
    "occurredAt": "2026-06-09T22:00:00.000Z",
    "data": {
      "searchId": "86bc0a43-6a10-4f85-a093-e4244ed78107",
      "externalSearchId": "EXT-DEV-1781042158637-1",
      "searchType": "EMPLOYMENT",
      "notificationType": "INBOUND_RECEIVED_DETAIL",
      "from": "test-provider",
      "classification": {
        "type": "VERIFICATION",
        "confidence": 0.94
      },
      "employmentVerification": {
          "confidence": 0.94,
          "fields": {
            "companyName": {
              "type": "string",
              "status": "match",
              "expected": "Pablo's Pizza",
              "extracted": "Pablos Pizza",
              "comparison": { "engine": "deterministic", "similarity": 1 }
            },
            "position": {
              "type": "string",
              "status": "match_with_discrepancy",
              "expected": "Senior Software Developer",
              "extracted": "Senior Software Engineer",
              "comparison": { "engine": "probabilistic", "similarity": 0.98 }
            },
            "startDate": {
              "type": "date",
              "status": "mismatch",
              "expected": "2022-01-01",
              "extracted": "2021-01-01",
              "comparison": { "engine": "deterministic", "distance": 365 }
            },
            "endDate": {
              "type": "date",
              "status": "match",
              "expected": "2023-01-01",
              "extracted": "2023-01-01",
              "comparison": { "engine": "deterministic", "distance": 0 }
            },
            "location": {
              "type": "string",
              "status": "match",
              "expected": "Grand Junction, CO",
              "extracted": "Grand Junction, CO",
              "comparison": { "engine": "deterministic", "similarity": 1 }
            },
            "applicantName": {
              "type": "string",
              "status": "match",
              "expected": "John Smith",
              "extracted": "John Smith",
              "comparison": { "engine": "deterministic", "similarity": 1 }
            }
        }
      },
      "metadata": {
        "source": "inbound",
        "channel": "JSON",
        "timestamp": "2026-06-09T22:00:00.000Z",
        "to": "JSON",
        "upstreamProvider": "test-provider",
        "extractedData": {
          "outcome": "verified",
          "refId": "EXT-DEV-1781042158637-1",
          "searchType": "EMPLOYMENT",
          "employerName": "Pablos Pizza",
          "jobTitle": "Senior Software Engineer",
          "startDate": "2021-01-01",
          "endDate": "2023-01-01",
          "employerLocation": "Grand Junction, CO",
          "extractedApplicantName": "John Smith",
          "_processedFromJson": true,
          "_thirdPartyProvider": "test-provider",
          "applicantMismatch": true,
          "_requiresManualReview": true
        }
      }
    }
  }
  ```

  ```json Discrepancies only (no top-level mismatch) theme={null}
  {
    "event": "verification.notification",
    "occurredAt": "2026-06-09T22:00:00.000Z",
    "data": {
      "searchId": "86bc0a43-6a10-4f85-a093-e4244ed78107",
      "externalSearchId": "EXT-DEV-1781042158637-1",
      "searchType": "EMPLOYMENT",
      "notificationType": "INBOUND_RECEIVED_DETAIL",
      "from": "test-provider",
      "classification": { "type": "VERIFICATION", "confidence": 0.94 },
      "employmentVerification": {
          "confidence": 0.94,
          "fields": {
            "companyName": {
              "type": "string",
              "status": "match",
              "expected": "Pablo's Pizza",
              "extracted": "Pablos Pizza",
              "comparison": { "engine": "deterministic", "similarity": 1 }
            },
            "position": {
              "type": "string",
              "status": "match_with_discrepancy",
              "expected": "Senior Software Developer",
              "extracted": "Senior Software Engineer",
              "comparison": { "engine": "probabilistic", "similarity": 0.98 }
            },
            "startDate": {
              "type": "date",
              "status": "match",
              "expected": "2022-01-01",
              "extracted": "2022-01-01",
              "comparison": { "engine": "deterministic", "distance": 0 }
            },
            "endDate": {
              "type": "date",
              "status": "match",
              "expected": "2023-01-01",
              "extracted": "2023-01-01",
              "comparison": { "engine": "deterministic", "distance": 0 }
            },
            "location": {
              "type": "string",
              "status": "match",
              "expected": "Grand Junction, CO",
              "extracted": "Grand Junction, CO",
              "comparison": { "engine": "deterministic", "similarity": 1 }
            },
            "applicantName": {
              "type": "string",
              "status": "match",
              "expected": "John Smith",
              "extracted": "John Smith",
              "comparison": { "engine": "deterministic", "similarity": 1 }
            }
        }
      },
      "metadata": {
        "source": "inbound",
        "channel": "JSON",
        "timestamp": "2026-06-09T22:00:00.000Z",
        "to": "JSON",
        "upstreamProvider": "test-provider",
        "extractedData": {
          "outcome": "verified",
          "refId": "EXT-DEV-1781042158637-1",
          "searchType": "EMPLOYMENT",
          "employerName": "Pablos Pizza",
          "jobTitle": "Senior Software Engineer",
          "startDate": "2022-01-01",
          "endDate": "2023-01-01",
          "employerLocation": "Grand Junction, CO",
          "extractedApplicantName": "John Smith",
          "_processedFromJson": true,
          "_thirdPartyProvider": "test-provider"
        }
      }
    }
  }
  ```
</CodeGroup>

Note: For JSON, root-level `employmentVerification` / `educationVerification` is the highest-confidence object. Field-level comparison for that selected row appears on `data.employmentVerification.fields` / `data.educationVerification.fields`. **`applicantMismatches` and `applicantDiscrepancies` are not included** in webhook or inbound notification payloads — use `fields.*` instead. When a critical mismatch triggers escalation, `metadata.extractedData` may include `applicantMismatch: true` and `_requiresManualReview: true` without field-level objects. When the search closes without escalation, `verification.completed` may set `verificationResult.discrepancy: false` even though `employmentVerification.fields.position.status` is `match_with_discrepancy` — see [Webhook events](/webhooks/events#example-employment-verification-json-channel).

### Auto-Generated IDs

When messageId or threadId are not provided, the system automatically generates them:

<CodeGroup>
  ```bash cURL theme={null}
  curl -sS -X POST "https://sandbox.theary.ai/background-check/v1/inbound/notifications" \
    -H "Authorization: Bearer YOUR_JWT_TOKEN" \
    -H "Content-Type: application/json" \
    -H "Accept: application/json" \
    -d '{
      "channel": "EMAIL",
      "messageData": {
        "from": "hr@company.com",
        "to": "verifications@theary.ai",
        "subject": "Employment Verification - John Doe",
        "body": {
          "text": "Reference: 11111111\n\nJohn Doe worked here from 2020-01-15 to 2023-12-31 as Software Engineer."
        },
        "timestamp": "2026-01-15T10:30:00.000Z"
      }
    }'
  ```

  **Note:** The system will automatically generate `messageId` and `threadId` if not provided. The generated IDs will be returned in the response.
</CodeGroup>

## Error Responses

<Note>
  This endpoint always returns HTTP `200` (the controller forces `@HttpCode(HttpStatus.OK)`), even when processing fails. Failures are represented in the response body, not the HTTP status — check the `success` and `failureReasons` fields on every response.
</Note>

<ResponseExample>
  ```json 200 - JSON Missing searchRefId theme={null}
  {
    "messageId": "msg_001",
    "success": false,
    "failureReasons": ["metadata.searchRefId is required for JSON channel requests"],
    "processedAt": "2024-01-15T10:30:00.000Z"
  }
  ```
</ResponseExample>

<ResponseExample>
  ```json 200 - JSON Missing Payload theme={null}
  {
    "messageId": "msg_001",
    "success": false,
    "failureReasons": ["No JSON payload found"],
    "processedAt": "2024-01-15T10:30:00.000Z"
  }
  ```
</ResponseExample>

<ResponseExample>
  ```json 200 - JSON Search Not Found theme={null}
  {
    "messageId": "msg_001",
    "success": false,
    "failureReasons": ["Search EXT-DEV-1781042158637-1 not found in verification system"],
    "processedAt": "2024-01-15T10:30:00.000Z"
  }
  ```
</ResponseExample>

<ResponseExample>
  ```json 200 - Search Not Found (non-JSON) theme={null}
  {
    "messageId": "msg_001",
    "success": false,
    "failureReasons": ["Search not found"],
    "processedAt": "2024-01-15T10:30:00.000Z"
  }
  ```
</ResponseExample>

<ResponseExample>
  ```json 400 - Missing Required Fields (non-JSON) theme={null}
  {
    "statusCode": 400,
    "message": "messageData.from, messageData.to required for EMAIL channel",
    "error": "Bad Request"
  }
  ```
</ResponseExample>

<ResponseExample>
  ```json 401 - Unauthorized theme={null}
  {
    "statusCode": 401,
    "message": "Unauthorized"
  }
  ```
</ResponseExample>

<ResponseExample>
  ```json 400 - Validation Error theme={null}
  {
    "statusCode": 400,
    "message": ["messageData.timestamp should not be empty", "channel must be a valid enum value"],
    "error": "Bad Request"
  }
  ```
</ResponseExample>

Validation failures on this request return the same generic `400 Bad Request` shape as every other endpoint — there is no `422` status configured for this API. `messageId` is optional and can never trigger a "should not be empty" failure; `messageData.timestamp` is the field actually marked as required.

<ResponseExample>
  ```json 400 - Missing Required Field for Channel theme={null}
  {
    "statusCode": 400,
    "message": "messageData.from, messageData.to required for EMAIL channel",
    "error": "Bad Request"
  }
  ```
</ResponseExample>

Non-`JSON` channels also require `messageData.from` and `messageData.to`; the controller checks this explicitly and returns a single-string `400` message (not the array shape) naming the missing fields.

## How It Works

The inbound notification processing follows these steps:

1. **Submit** - You send verification response via API
2. **ID Generation** - If `messageId` or `threadId` are not provided, they are auto-generated
3. **Validate** - System validates required fields and checks for malicious content. For `JSON` channel, validates that `metadata.searchRefId` and a non-empty `messageData.data` payload are present. For other channels, validates `messageData.from` and `messageData.to`.
4. **Match** - Links message to active verification search:
   * **`JSON` channel:** Matches exclusively on `metadata.searchRefId` (external search ID or internal reference). Does not scan the payload body for reference numbers.
   * **Other channels:** Extracts 7–8 digit reference numbers from subject, body, attachments, or uses `metadata.searchRefId` / `threadId` when provided.
5. **Process Attachments** - PDF attachments are processed to extract text content (if present; skipped for `JSON` channel)
6. **Analyze Contact** - Determines sender type (APPLICANT, ENTITY, or UNKNOWN). JSON channel requests are treated as ENTITY responses.
7. **Classify & Extract** - AI-powered classification extracts verification details:
   * **`JSON` channel:** Extracts directly from structured provider payload; selects the best-matching employment or education record when multiple are present.
   * **Other channels:** Analyzes the message content to extract structured verification data.
   * Extracts structured outcome (VERIFIED, NO\_RECORD, THIRD\_PARTY, INFO\_REQUESTED, etc.)
   * Parses verification data (dates, positions, confirmation status)
8. **Cross-check Provided Data** - Compares extracted applicant, employment, or education details against the order data on file. Tenant **comparison** settings (see tenant `config.comparisonConfig`) tune behavior: **calendar-day** windows per date field (`startDate`, `endDate`, `graduationDate`), **semantic equivalence** thresholds for applicant name, job title, degree, major, and location proximity, and **salary** tolerances. For employment **salary**, when both the order value (`providedSalary` in search metadata) and the extracted salary parse, the API normalizes common compensation text (for example annual, hourly, monthly, weekly, and `k` / `m` suffixes) to an estimated **annual USD** amount and treats a mismatch only if the gap exceeds **both** the configured absolute-dollar threshold and the relative (percent) threshold; otherwise it falls back to the same normalized string comparison used for other plain-text fields. Mismatch reasons for dates and salaries include the compared raw values (and annualized figures for salary) to aid review.
9. **Persist Results** - Saves processing results, classification, and extracted data
10. **Update Search** - Updates search status if terminal outcome (VERIFIED, NO\_RECORD, THIRD\_PARTY, WRONG\_ORG)
11. **Schedule Follow-up** - If non-terminal outcome (INFO\_REQUESTED, INSUFFICIENT), schedules next outbound attempt
12. **Notify** - Sends webhook if verification reaches terminal state

## Common Use Cases

| Use Case        | Channel           | Best For                                 |
| --------------- | ----------------- | ---------------------------------------- |
| Email from HR   | `EMAIL`           | Employment verification responses        |
| University form | `EFORM`           | Education verification with transcripts  |
| Fax response    | `FAX_WEBHOOK`     | Legacy systems, healthcare, legal        |
| Office 365      | `MICROSOFT_GRAPH` | Enterprise email automation              |
| Help desk       | `TICKETING`       | Support ticket workflows                 |
| Generic JSON    | `JSON`            | Structured third-party provider payloads |

## Best Practices

**Message IDs:** <br />

* Use unique identifiers (UUIDs recommended) if available <br />
* Never reuse message IDs <br />
* **Optional**: Omit the field entirely if you don't have one - the system will auto-generate it
* If provided, ensure it's unique across all messages

**Thread IDs:** <br />

* Keep consistent for related messages in the same conversation thread <br />
* Helps track conversation history across multiple exchanges <br />
* **Optional**: Omit the field entirely if you don't have one - the system will use the messageId as the threadId
* Use the same threadId for all messages in the same conversation

**Timestamps:** <br />

* Always use ISO 8601 format <br />
* Example: `2026-01-15T10:30:00.000Z`

**Content (email, fax, form, ticketing channels):** <br />

* Include all verification details in the `body.text` field <br />
* **Include reference number**: The message must contain a 7-8 digit reference number to match the message to the correct verification search. The system looks for patterns like:
  * "Reference: 12345678"
  * "Ref. 12345678"
  * "Ticket # 12345678"
  * "Case # 12345678"
  * Or standalone 7-8 digit numbers
* Mention attachments sent via separate channels if needed

**Content (`JSON` channel):** <br />

* Set `channel` to `"JSON"` and place the vendor response in `messageData.data` <br />
* **Always include `metadata.searchRefId`** — this is the only search-matching mechanism for JSON requests. Use the search's external ID (e.g. `EXT-DEV-1781042158637-1`) or internal reference number <br />
* Set `metadata.provider` to identify the upstream system for audit and traceability <br />
* Pass the vendor payload as-is; normalization to a single schema is not required <br />
* When the provider returns multiple employment or education records, include all of them — the API selects the record that best matches the applicant and submitted search data

**Error Handling:** <br />

* For every channel, HTTP 200 includes a response body; check `success` and process `failureReasons` when present <br />
* Successful responses include best-match scalars in `extractedData` and the highest-confidence normalized provider record in the
  applicable root-level verification object <br />
* Implement retry with exponential backoff for server errors

## Troubleshooting

**"metadata.searchRefId is required for JSON channel requests"** <br />

* Include `metadata.searchRefId` on every `channel: "JSON"` request <br />
* The value must be a non-empty string

**"No JSON payload found"** <br />

* Ensure `messageData.data` is a non-empty object containing at least one enumerable employment or education record <br />
* Do not send only `{ "text": "..." }` or `{ "html": "..." }` keys without structured verification data

**"Search {ref} not found in verification system" (JSON channel)** <br />

* Verify `metadata.searchRefId` matches the search's `externalSearchId` or internal reference <br />
* Ensure the search is still active (not completed or cancelled) <br />
* Check that the search has the INBOUND objective enabled

**"Search not found" (non-JSON channels)** <br />

* Verify the message contains a valid 7-8 digit reference number in one of the supported formats:
  * "Reference: 12345678"
  * "Ref. 12345678"
  * "Ticket # 12345678"
  * "Case # 12345678"
  * Or a standalone 7-8 digit number
* Ensure the reference number matches an active verification search
* Ensure search is still active (not completed or cancelled)
* Check that the search has the INBOUND objective enabled
* Check sender email matches expected contact (if applicable)

**"messageData.from, messageData.to required for {channel} channel"** <br />

* Non-JSON channels require both `messageData.from` and `messageData.to` <br />
* These fields are optional only when `channel` is `"JSON"`

**"Invalid message format"** <br />

* All required fields must be present for the channel type <br />
* Check timestamp is valid ISO 8601

**"Validation error"** <br />

* Review error messages in response <br />
* Verify data types match requirements <br />
* Ensure all required fields are present

## Next Steps

* [Configure webhook endpoints](/api-reference/schemas/webhook-config) <br />
* [Learn about webhook events](/webhooks/events) <br />
* [Understand error handling](/guides/error-handling)


## OpenAPI

````yaml POST /background-check/v1/inbound/notifications
openapi: 3.0.0
info:
  title: SNH AI Background Check API
  description: >-
    AI-powered agentic background verification service for employment and
    education checks.
  version: '1.0'
  contact: {}
servers:
  - url: https://sandbox.theary.ai
    description: Sandbox
  - url: https://api.theary.ai
    description: Production
  - url: http://localhost:3000
    description: Local development
security:
  - bearerAuth: []
tags: []
paths:
  /background-check/v1/inbound/notifications:
    post:
      tags:
        - Inbound Notifications
      summary: Submit a standardized inbound notification
      description: >-
        Accepts a standard inbound notification payload from external systems
        (e.g., email, ticketing, Jitbit, eForms). Message and thread IDs are
        auto-generated if not provided.
      operationId: handle-inbound-notification
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InboundProcessingDto'
            examples:
              employmentResponse:
                value:
                  messageData:
                    from: hr@techcorp.com
                    to: verify@theary.ai
                    subject: 'Re: Employment Verification Request for John Smith'
                    body:
                      text: >-
                        John Smith was employed at Tech Corp from January 2020
                        to June 2023 as a Software Engineer.
                      html: >-
                        <p>John Smith was employed at Tech Corp from January
                        2020 to June 2023 as a Software Engineer.</p>
                    timestamp: '2026-01-10T10:30:00Z'
                  metadata:
                    searchId: search_abc123
                    applicantName: John Smith
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
      security:
        - bearerAuth: []
components:
  schemas:
    InboundProcessingDto:
      type: object
      properties:
        messageId:
          type: string
          description: Message ID to process (auto-generated if not provided)
        threadId:
          type: string
          description: Thread ID (auto-generated if not provided)
        channel:
          type: string
          description: Channel type
          enum:
            - MICROSOFT_GRAPH
            - FAX_WEBHOOK
            - STANDARD
            - EMAIL
            - TICKETING
            - JITBIT
            - EFORM
        messageData:
          description: Message data
          allOf:
            - $ref: '#/components/schemas/MessageDataDto'
        metadata:
          type: object
          description: >-
            Additional metadata. Can include pre-extracted data from upstream
            systems:
                - provider: Source system identifier (e.g., 'SNH AI ', 'JITBIT')
                - searchRefId: Reference ID for the search
                - activityId: Activity ID from upstream system
                - link: URL to original ticket/message
                - intention: Pre-classified intent (VERIFICATION, INFO_REQUEST, THIRD_PARTY, etc.)
                - confidence: Confidence level from upstream (0-1). Defaults to 0.95 if not provided.
                - structuredData: Pre-extracted verification data with verifiedInfo and verificationResult
                
                When structuredData is provided with verifiedInfo, the system will use this
                pre-extracted data instead of re-processing, improving accuracy and efficiency.
                
                Supported verificationResult values: VERIFIED, VERIFIED_DISCREPANCY, VERIFIED_NO_DISCREPANCY,
                UNABLE_TO_VERIFY, UNVERIFIED, NO_RECORD, RECORD_NOT_FOUND, THIRD_PARTY, WRONG_ENTITY
          example:
            provider: UPSTREAM_SYSTEM
            searchRefId: '12345678'
            intention: VERIFICATION
            confidence: 0.95
            structuredData:
              verifiedInfo:
                subjectName: John Smith
                employerName: Tech Corp
                startDate: '2020-01-15'
                endDate: '2023-06-30'
                position: Software Engineer
              verificationResult: VERIFIED
      required:
        - messageData
      title: Inbound Processing
    MessageDataDto:
      type: object
      properties:
        id:
          type: string
          description: Message ID (auto-generated if not provided)
        threadId:
          type: string
          description: Thread ID (auto-generated if not provided)
        from:
          type: string
          description: From email address or form identifier
        to:
          type: string
          description: To email address or recipient identifier
        subject:
          type: string
          description: Email subject or form title
        body:
          type: object
          description: Message body or form data
        headers:
          type: object
          description: Email headers or additional metadata
        attachments:
          description: File attachments with optional base64 content
          type: array
          items:
            $ref: '#/components/schemas/MessageAttachmentDto'
        timestamp:
          type: string
          description: Message timestamp
      required:
        - from
        - to
        - timestamp
      title: Message Data
    MessageAttachmentDto:
      type: object
      properties:
        id:
          type: string
          description: Attachment identifier
        name:
          type: string
          description: Original file name
        filename:
          type: string
          description: Alternative file name
        contentType:
          type: string
          description: MIME content type (e.g. application/pdf)
        mimeType:
          type: string
          description: Alternative MIME type field
        size:
          type: number
          description: File size in bytes
        content:
          type: string
          description: Base64-encoded file content
      required:
        - id
        - name
        - contentType
        - size
      title: Message Attachment
  securitySchemes:
    bearerAuth:
      scheme: bearer
      bearerFormat: JWT
      type: http

````