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

# Evaluate

> Evaluate a single criminal record for compliance and reportability

<Tip>
  Initial submissions: `record.xml` + `record.candidate_info`. Resubmissions: `record.submission_type` (`"resubmit"`) + `record.record_json` + `record.candidate_info`. Both paths require `search_id`, `search_date`, `order_id`, and `order_number` on `record`. The response shape is unchanged for both paths.
</Tip>

## Input modes: XML vs JSON

The `record` object accepts two **mutually exclusive** input types:

| Input                                                                     | Use case                                      |
| ------------------------------------------------------------------------- | --------------------------------------------- |
| `record.xml` + `record.candidate_info`                                    | Initial submission — raw vendor XML           |
| `record.submission_type` + `record.record_json` + `record.candidate_info` | Resubmission — corrected mapped criminal JSON |

For resubmissions, send sibling fields on `record`: `submission_type` (`"resubmit"`), `record_json` (mapped criminal data only), `candidate_info`, plus `search_id`, `search_date`, `order_id`, and `order_number`.

`submission_type: "resubmit"` is **required** whenever `record_json` is provided, and must be a top-level field on `record` — not inside `record_json` or `record_json.search`. Initial submissions use `record.xml` + `record.candidate_info` with no `submission_type` (or omit it).

## Request

<ParamField header="Authorization" type="string" required>
  `Bearer YOUR_JWT_TOKEN` — see [Authentication](/authentication)
</ParamField>

<ParamField body="record" type="object" required>
  The criminal record to evaluate. Provide exactly one of `xml` (initial submission) or `record_json` (resubmission), plus search metadata.

  <Expandable title="Record Properties">
    <ParamField body="search_id" type="string" required>
      Unique identifier for this search
    </ParamField>

    <ParamField body="search_date" type="string" required>
      Date of search in `YYYY-MM-DD` format
    </ParamField>

    <ParamField body="order_id" type="string" required>
      Order ID from the requesting system — top-level on `record` alongside `search_id` and `search_date`. Required, non-empty string. Must **not** be `null`, empty (`""`), or nested inside `record_json` or `record_json.search`. Echoed in the response.
    </ParamField>

    <ParamField body="order_number" type="string" required>
      UBS order number (format: `orderID.subOrderID`) — top-level on `record`. Required, non-empty string. Must **not** be `null`, empty (`""`), or nested inside `record_json` or `record_json.search`.
    </ParamField>

    <ParamField body="xml" type="string">
      Raw XML string from the screening provider (`<ScreeningResults>...</ScreeningResults>`). **Initial submission** — provide this or `record_json`, not both.
    </ParamField>

    <ParamField body="record_json" type="object">
      Mapped criminal record JSON — **resubmission**. Must include top-level `criminal_search_results`. Must **not** include `candidate_info`, `submission_type`, `order_id`, or `order_number` — provide those as sibling fields on `record`. Requires `submission_type: "resubmit"` on `record`. Mutually exclusive with `xml`.
    </ParamField>

    <ParamField body="submission_type" type="string">
      Resubmission flag. **Required** when `record_json` is provided — must be `"resubmit"`. Omit on initial XML submissions. Must be a top-level field on `record`, not inside `record_json` or `record_json.search`.
    </ParamField>

    <ParamField body="tenant_id" type="string">
      Organization identifier for authentication and data isolation
    </ParamField>

    <ParamField body="applicant_state" type="string">
      Two-letter state code where the applicant will work (e.g., `"CA"`, `"TX"`). Used for state-specific compliance rules.
    </ParamField>

    <ParamField body="cases" type="array">
      **Optional** full charge mapping — omit entirely or send `[]` to evaluate **all** offenses (count-reconciliation gate not applied). When non-empty, `cases[]` must enumerate **every** court and offense in the parsed XML or `record_json` — counts must match exactly. Partial or filtered subsets are rejected.

      <Warning>
        **Positional Mapping Requirement:** The `cases[]` entries are mapped to charges in the vendor XML by **sequential position**. You **must** include an entry for every charge in the XML, in the same order the charges appear, even for charges that don't need a decision. For charges to exclude from the response, set `is_excluded: true`. Sending fewer entries than XML charges causes positional drift — identifiers will be mapped to the wrong cases.
      </Warning>

      <Expandable title="Case Mapping Properties">
        <ParamField body="court_search_id" type="string" required>
          Court search identifier from the record (e.g., `"CS40929433A"`). Required on each row when `cases` is non-empty — must be non-empty. Multiple rows may share the same `court_search_id` when that court has multiple offenses (1 court : many offenses).
        </ParamField>

        <ParamField body="offense_id" type="string" required>
          Offense identifier from the record (e.g., `"34789896"`). Required on each row when `cases` is non-empty — must be non-empty. Total `offense_id` rows must equal the total number of offenses/charges in the record.
        </ParamField>

        <ParamField body="is_excluded" type="boolean">
          If `true`, the charge is excluded from the response — no decision is returned for it. All charges and cases are still processed on the backend. Defaults to `false` if omitted. Use this to include all charges in the array (preserving positional alignment with the vendor XML) while excluding specific charges from the response.
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField body="offense_ids" type="array">
      Deprecated — use `cases` for full charge mapping.
    </ParamField>

    <ParamField body="candidate_info" type="object" required>
      Candidate identity for identity matching and compliance. **Required** on `record` for both initial XML and resubmission paths — non-empty object (not `null`, missing, or `{}`). Must **not** be nested inside `record_json`. Use the flat request shape below. Do not send `aliases[]` or `addresses[]` on the request.

      <Expandable title="Candidate Info Properties">
        <ParamField body="first_name" type="string" required>
          Candidate first name. Required, non-empty string. Example: `"John"`
        </ParamField>

        <ParamField body="middle_name" type="string">
          Middle name or initial. Example: `"A"`
        </ParamField>

        <ParamField body="last_name" type="string" required>
          Last name. Required, non-empty string. Example: `"Doe"`
        </ParamField>

        <ParamField body="date_of_birth" type="string" required>
          Date of birth in `YYYY-MM-DD` format. Required, non-empty string. Example: `"2000-01-01"`
        </ParamField>

        <ParamField body="ssn" type="string">
          Social Security Number — any format, normalized internally. Example: `"123-45-6789"`
        </ParamField>

        <ParamField body="address" type="string">
          Single-line full address. Example: `"123 Main St, Austin, TX 78701"`
        </ParamField>

        <ParamField body="source_type" type="string">
          Optional pass-through audit field — no fixed enum
        </ParamField>

        <ParamField body="source_reliability_rating" type="string">
          Optional pass-through audit field
        </ParamField>

        <ParamField body="verification_status" type="string">
          Optional pass-through audit field
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField body="search_type" type="string">
      UBS search type. Examples: `"Statewide_criminal"`, `"County_criminal"`
    </ParamField>
  </Expandable>
</ParamField>

### Request validation

| Rule                                 | Detail                                                                                                                                                                                                                                                                                                              |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Exactly one input                    | Must provide exactly one of `xml` or `record_json` (not both, not neither)                                                                                                                                                                                                                                          |
| `record_json` shape                  | Must contain top-level `criminal_search_results` — non-empty array; each item and nested `criminal_records[]` entry must include all required fields (not `null`, missing, or `""`) — see [`record_json` reference](#record_json-reference)                                                                         |
| `submission_type` with `record_json` | `record_json` requires `submission_type: "resubmit"` on `record`                                                                                                                                                                                                                                                    |
| `cases[]`                            | **Optional** — omit or send `[]` to evaluate all offenses (gate not applied). When non-empty, distinct `court_search_id` count must equal the number of XML/record\_json cases with at least one offense, and total `offense_id` row count must equal total offenses/charges — partial subsets rejected (see below) |
| `candidate_info`                     | Required on `record` — non-empty object (not `null`, missing, or `{}`); must include non-empty `first_name`, `last_name`, and `date_of_birth`; must **not** be inside `record_json`                                                                                                                                 |
| `order_id` / `order_number`          | Required on `record` — non-empty strings (not `null`, missing, or `""`); must **not** be inside `record_json` or `record_json.search`                                                                                                                                                                               |

Common **HTTP 400** errors:

| Message                                                                                 | Cause                                                                                                          |
| --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `Either xml or record_json is required`                                                 | Neither input field was provided                                                                               |
| `Provide exactly one of xml and record_json`                                            | Both fields were provided                                                                                      |
| `record_json must contain criminal_search_results`                                      | `record_json` is missing the required top-level key                                                            |
| `criminal_search_results must not be empty`                                             | `criminal_search_results` is `null`, missing, or `[]`                                                          |
| `court_search_id must not be empty`                                                     | `court_search_id` is `null`, missing, or `""` in `record_json.criminal_search_results[]`                       |
| `search_date must not be empty`                                                         | `search_date` is `null`, missing, or `""` in `record_json.criminal_search_results[]`                           |
| `criminal_records must not be empty`                                                    | `criminal_records` is `null`, missing, or `[]` in `record_json.criminal_search_results[]`                      |
| `case_number must not be empty`                                                         | `case_number` is `null`, missing, or `""` in `record_json.criminal_search_results[].criminal_records[]`        |
| `jurisdiction_state must not be empty`                                                  | `jurisdiction_state` is `null`, missing, or `""` in `record_json.criminal_search_results[].criminal_records[]` |
| `file_date must not be empty`                                                           | `file_date` is `null`, missing, or `""` in `record_json.criminal_search_results[].criminal_records[]`          |
| `subject_name must not be empty`                                                        | `subject_name` is `null`, missing, or `""` in `record_json.criminal_search_results[].criminal_records[]`       |
| `sub_first_name must not be empty`                                                      | `sub_first_name` is `null`, missing, or `""` in `record_json.criminal_search_results[].criminal_records[]`     |
| `sub_middle_name must not be empty`                                                     | `sub_middle_name` is `null`, missing, or `""` in `record_json.criminal_search_results[].criminal_records[]`    |
| `sub_last_name must not be empty`                                                       | `sub_last_name` is `null`, missing, or `""` in `record_json.criminal_search_results[].criminal_records[]`      |
| `subject_dob must not be empty`                                                         | `subject_dob` is `null`, missing, or `""` in `record_json.criminal_search_results[].criminal_records[]`        |
| `subject_ssn must not be empty`                                                         | `subject_ssn` is `null`, missing, or `""` in `record_json.criminal_search_results[].criminal_records[]`        |
| `subject_address must not be empty`                                                     | `subject_address` is `null`, missing, or `""` in `record_json.criminal_search_results[].criminal_records[]`    |
| `offenses must not be empty`                                                            | `offenses` is `null`, missing, or `[]` in `record_json.criminal_search_results[].criminal_records[]`           |
| `submission_type must be 'resubmit' when record_json is provided`                       | `record_json` sent without `submission_type: "resubmit"`                                                       |
| `submission_type 'resubmit' requires record_json, not xml`                              | `submission_type: "resubmit"` sent with `xml` instead of `record_json`                                         |
| `submission_type must not be inside record_json; provide it as a separate field`        | `submission_type` nested inside `record_json`                                                                  |
| `submission_type must not be inside record_json.search; provide it as a separate field` | `submission_type` nested inside `record_json.search`                                                           |
| `candidate_info must not be inside record_json; provide it as a separate field`         | `candidate_info` was nested inside `record_json`                                                               |
| `candidate_info must not be empty`                                                      | `candidate_info` is `null`, missing, or `{}`                                                                   |
| `candidate_info requires at minimum: first_name, last_name, and date_of_birth`          | Required `candidate_info` fields are missing or empty (`""`)                                                   |
| `order_id must not be inside record_json; provide it as a separate field`               | `order_id` nested inside `record_json` or `record_json.search`                                                 |
| `order_number must not be inside record_json; provide it as a separate field`           | `order_number` nested inside `record_json` or `record_json.search`                                             |
| `order_id must not be empty`                                                            | `order_id` is `null`, missing, or `""`                                                                         |
| `order_number must not be empty`                                                        | `order_number` is `null`, missing, or `""`                                                                     |

Common **HTTP 422** errors (XML-Transformer — court search ID / offense ID count mismatch):

| Message                                                                                                                                                                                                                                                                                                                               | Cause                                                                                                                                                                 |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Court search ID and offense ID counts do not match the XML: {n} court search ID(s) in the request but {m} case(s) in the XML; {p} offense ID(s) in the request but {q} offense(s) in the XML. Each court search ID must map to exactly one XML case and each offense ID to exactly one XML offense; resubmit with the complete set.` | Non-empty `cases[]` — distinct court search ID count or total offense ID row count does not match parsed XML/record\_json (`error_code: CASE_OFFENSE_COUNT_MISMATCH`) |

<Note>
  This validation runs inside XML-Transformer and returns HTTP **422** on `/transform-xml`. On `POST /evaluate`, the orchestrator still returns HTTP **500** with `data.status: "failed"` and embeds the XT error as `XML-Transformer HTTP error 422: ...` in `data.errors[]`.
</Note>

### `cases[]` count reconciliation (XML-Transformer gate)

When `record.cases[]` is **non-empty**, XML-Transformer validates that counts match the parsed record **before** enrichment or compliance evaluation. Processing stops immediately on mismatch.

| Check                    | Rule                                                                                                                           |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
| Distinct courts          | Number of **distinct** `court_search_id` values in `cases[]` = number of XML/record\_json cases that have at least one offense |
| Total offenses           | Number of `offense_id` entries in `cases[]` = total number of XML/record\_json offenses/charges                                |
| One court, many offenses | Multiple `cases[]` rows may share the same `court_search_id` (1 court : N offenses)                                            |
| Partial subsets          | **Rejected** — you cannot send a filtered subset of courts or offenses                                                         |
| Omit or empty            | Omit `cases[]` or send `cases: []` — evaluate all offenses; gate **not** applied                                               |

**Matching example** — XML has 2 cases and 3 charges:

```json theme={null}
[
  { "court_search_id": "A", "offense_id": "1", "is_excluded": false },
  { "court_search_id": "A", "offense_id": "2", "is_excluded": true },
  { "court_search_id": "B", "offense_id": "3", "is_excluded": false }
]
```

Offense `"2"` is included in the array to maintain positional alignment but excluded from the response (`is_excluded: true`). All three charges are still processed.

**Mismatch example** — UBS sends 17 distinct `court_search_id` values and 22 `offense_id` rows, but XML has 24 cases and 32 offenses → rejected.

<Warning>
  This gate is **not** surfaced as HTTP **422** on `POST /evaluate`. XML-Transformer returns HTTP **422** internally (`error_code: CASE_OFFENSE_COUNT_MISMATCH`); the orchestrator surfaces HTTP **500** with `data.status: "failed"`. See [Example Response — Failed (court search ID / offense ID count mismatch)](#example-response--failed-court-search-id--offense-id-count-mismatch).
</Warning>

<Note>
  The `record_json` structure errors above apply when `record_json` is provided (resubmission path). The `sentence` block on offenses is **optional** and is not validated as required on input. Other offense-level fields are validated separately during enrichment.
</Note>

## Response

All responses are wrapped in a standard envelope:

<ResponseField name="success" type="boolean">
  `true` when `status` is `success` or `partial`. `false` on `failed`.
</ResponseField>

<ResponseField name="data" type="object">
  The evaluation result payload.

  <Expandable title="Data Properties">
    <ResponseField name="search_id" type="string">
      Unique identifier for the search
    </ResponseField>

    <ResponseField name="correlation_id" type="string">
      Unique ID generated by SNH AI for each request. Include this when contacting support for troubleshooting.
    </ResponseField>

    <ResponseField name="status" type="string">
      Processing status: `success` (all engines completed), `partial` (some engines failed), or `failed`
    </ResponseField>

    <ResponseField name="errors" type="array">
      Service-level errors (e.g., engine timeouts, network failures). Empty array `[]` when no errors occurred. These are different from data-quality issues, which appear under `validation`.
    </ResponseField>

    <ResponseField name="validation" type="object">
      Data quality issues found during processing. **Only present when issues exist** — omitted entirely from the response when the source data is clean.

      <Expandable title="Validation Properties">
        <ResponseField name="status" type="string">
          Overall validation status (e.g., `needs_manual_review`)
        </ResponseField>

        <ResponseField name="issues" type="object">
          Container for the structured issue lists.

          <Expandable title="Issues Properties">
            <ResponseField name="errors" type="array">
              Hard data-quality problems (missing SSN, missing address, indeterminate disposition class, etc.). Each item:

              <Expandable title="Error Item Properties">
                <ResponseField name="level" type="string">
                  Where the issue was found: `candidate`, `case_subject`, `case`, or `offense`
                </ResponseField>

                <ResponseField name="field" type="string">
                  The specific field with the issue (e.g., `subject_ssn`, `disposition_class`)
                </ResponseField>

                <ResponseField name="issue" type="string">
                  Short human-readable description
                </ResponseField>

                <ResponseField name="description" type="string">
                  Long-form explanation of why the field matters
                </ResponseField>

                <ResponseField name="case_id" type="string">
                  Case identifier (empty string when not applicable)
                </ResponseField>

                <ResponseField name="case_number" type="string">
                  Case number associated with the issue
                </ResponseField>

                <ResponseField name="offense_id" type="string">
                  Offense identifier (present only for offense-level issues)
                </ResponseField>
              </Expandable>
            </ResponseField>

            <ResponseField name="warnings" type="array">
              Soft mismatches (e.g., disposition class disagrees between rule engine and ML model). Same item shape as `errors`.
            </ResponseField>
          </Expandable>
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="decision" type="object">
      The evaluation result containing the decision hierarchy.

      <Expandable title="Decision Properties">
        <ResponseField name="search_id" type="string">
          Unique identifier for the search
        </ResponseField>

        <ResponseField name="decision_date" type="string">
          ISO 8601 UTC timestamp of when the decision was produced. Example: `"2026-05-19T14:22:10Z"`
        </ResponseField>

        <ResponseField name="record_decision" type="string">
          Record-level reportability label. Values: `REPORTABLE`, `NOT_REPORTABLE`, `MANUAL_REVIEW`. At aggregated levels (`record_decision`, `court_decision`, `case_decision`), this is **derived from the matching `*_queue`**: `Automation` → `NOT_REPORTABLE`, `Auditor` → `REPORTABLE`, `Insufficient Data` → `MANUAL_REVIEW`.
        </ResponseField>

        <ResponseField name="search_queue" type="string">
          Record-level routing queue, aggregated from every charge. Values: `Automation`, `Auditor`, `Insufficient Data`. Precedence: `Insufficient Data` > `Auditor` > `Automation`.
        </ResponseField>

        <ResponseField name="order_id" type="string">
          Order identifier (populated when included in request)
        </ResponseField>

        <ResponseField name="order_number" type="string">
          UBS order number in `orderID.subOrderID` form (populated when supplied in the request)
        </ResponseField>

        <ResponseField name="ubs_search_type" type="string">
          UBS search-type classification, echoed back from the request
        </ResponseField>

        <ResponseField name="court_decisions" type="array">
          Array of court-level decisions

          <Expandable title="Court Properties">
            <ResponseField name="court_search_id" type="string">
              Court search identifier from the screening vendor. Example: `"111111"`, `"TBD"` when not supplied.
            </ResponseField>

            <ResponseField name="court_decision" type="string">
              Court-level reportability. Values: `REPORTABLE`, `NOT_REPORTABLE`, `MANUAL_REVIEW` — derived from `court_queue` (see `record_decision`).
            </ResponseField>

            <ResponseField name="court_queue" type="string">
              Court-level routing queue. Values: `Automation`, `Auditor`, `Insufficient Data`.
            </ResponseField>

            <ResponseField name="case_decisions" type="array">
              Array of case-level decisions

              <Expandable title="Case Properties">
                <ResponseField name="case_number" type="string">
                  Case number from the court record. Example: `"2009D 008764"`, `"2024-CF-00312"`
                </ResponseField>

                <ResponseField name="case_decision" type="string">
                  Case-level reportability. Values: `REPORTABLE`, `NOT_REPORTABLE`, `MANUAL_REVIEW` — derived from `case_queue` (see `record_decision`).
                </ResponseField>

                <ResponseField name="case_queue" type="string">
                  Case-level routing queue. Values: `Automation`, `Auditor`, `Insufficient Data`.
                </ResponseField>

                <ResponseField name="jurisdiction_state" type="string">
                  Two-letter state code. Example: `"TX"`, `"NC"`, `"CA"`
                </ResponseField>

                <ResponseField name="id_match" type="object">
                  Identity match scores comparing candidate against record subject

                  <Expandable title="Identity Match Properties">
                    <ResponseField name="is_match" type="boolean">
                      Whether candidate and record subject match. Boolean from the identity-match service (threshold varies by deployment — check `details` for the qualify percentage).
                    </ResponseField>

                    <ResponseField name="match_score" type="number">
                      Overall weighted score. Example: `0.85`, `0.925`, `0.40` (range 0–1)
                    </ResponseField>

                    <ResponseField name="name_score" type="number">
                      Name/alias match score (0–1 typically)
                    </ResponseField>

                    <ResponseField name="dob_score" type="number">
                      Date of birth match score (0–1 typically)
                    </ResponseField>

                    <ResponseField name="ssn_score" type="number">
                      SSN match score (0–1 typically)
                    </ResponseField>

                    <ResponseField name="address_score" type="number">
                      Address match score. Example: `1`, `0`, or `-1` when address was not scored
                    </ResponseField>

                    <ResponseField name="details" type="string">
                      Human-readable breakdown of each component score
                    </ResponseField>
                  </Expandable>
                </ResponseField>

                <ResponseField name="offenses" type="array">
                  Array of per-charge decisions

                  <Expandable title="Offense Properties">
                    <ResponseField name="offense_id" type="string">
                      Charge identifier from the screening XML source data
                    </ResponseField>

                    <ResponseField name="charge" type="string">
                      Charge description (e.g., `"NO LIABILITY INSURANCE"`)
                    </ResponseField>

                    <ResponseField name="charge_decision" type="string">
                      Per-charge reportability. Values: `REPORTABLE`, `NOT_REPORTABLE`, `MANUAL_REVIEW`
                    </ResponseField>

                    <ResponseField name="type" type="string">
                      Offense classification. Examples: `"Misdemeanor"`, `"Felony"`, `"FELONY"`, `"M"`, `"D"`
                    </ResponseField>

                    <ResponseField name="disposition" type="string">
                      Disposition text. Examples: `"Guilty"`, `"GUILTY"`, `"Dismissed"`, `"Pending"`, `"Deferred"`, `"No Information"`
                    </ResponseField>

                    <ResponseField name="rationale" type="string">
                      Human-readable explanation of the compliance decision — use this as the note/comment for the charge in your system
                    </ResponseField>

                    <ResponseField name="needs_human_review" type="boolean">
                      `true` when `charge_decision` is `MANUAL_REVIEW`
                    </ResponseField>

                    <ResponseField name="cited_rules" type="array">
                      Rule IDs that fired during evaluation (e.g., `GENERAL_TRAFFIC_NON_REPORT`, `CA_NONCONV_7YR_LIMIT`)
                    </ResponseField>

                    <ResponseField name="citations" type="array">
                      Legal statute references (e.g., `"15 U.S.C. § 1681c(a)(5)"`). May be empty when the cited rules do not include explicit statute references.
                    </ResponseField>

                    <ResponseField name="routing" type="object">
                      Automation routing assignment

                      <Expandable title="Routing Properties">
                        <ResponseField name="queue" type="string">
                          Routing queue. Values: `Automation`, `Auditor`, `Insufficient Data`
                        </ResponseField>

                        <ResponseField name="reportability" type="string">
                          Values: `Reportable`, `Not Reportable`, `Not Enough Info`
                        </ResponseField>

                        <ResponseField name="is_automatable" type="boolean">
                          `true` only when queue is `Automation`
                        </ResponseField>

                        <ResponseField name="identity_level" type="string">
                          Values: `High`, `Medium`, `Not Enough Info`, `Not Matching`. Derived from `identity_score`: ≥ 0.9 → `High`; 0.7–0.89 → `Medium`; ≤ −1 → `Not Matching`; otherwise → `Not Enough Info`.
                        </ResponseField>

                        <ResponseField name="identity_score" type="number">
                          Identity match score (0–1). Example: `0.925`, `0.85`, `0.40`
                        </ResponseField>

                        <ResponseField name="identity_insufficient" type="boolean">
                          `true` when identity data is too sparse
                        </ResponseField>

                        <ResponseField name="reportability_insufficient" type="boolean">
                          `true` when offense data is too sparse
                        </ResponseField>

                        <ResponseField name="third_id_required" type="boolean">
                          `true` when a third identity factor is required for routing
                        </ResponseField>
                      </Expandable>
                    </ResponseField>
                  </Expandable>
                </ResponseField>
              </Expandable>
            </ResponseField>
          </Expandable>
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="timing" type="object">
      Processing time breakdown in milliseconds. Includes `total_ms` and per-component timing.
    </ResponseField>

    <ResponseField name="degradation" type="object">
      Only present when `status` is `partial` or `failed`. Shows per-component availability status. Values include `available`, `degraded`, `unavailable`, `failed`, and `not_called` (e.g., compliance engine not invoked when XML-Transformer fails on `cases[]` count mismatch).
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="meta" type="object">
  Request metadata.

  <Expandable title="Meta Properties">
    <ResponseField name="api_version" type="string">
      API version
    </ResponseField>

    <ResponseField name="process_time_ms" type="number">
      Total wall-clock request time in milliseconds
    </ResponseField>

    <ResponseField name="correlation_id" type="string">
      Same correlation ID as in the data payload
    </ResponseField>
  </Expandable>
</ResponseField>

### HTTP Status Codes

| Status  | Meaning                                                                                                                                                                                                                                              |
| ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **200** | `success` — all processing completed                                                                                                                                                                                                                 |
| **206** | `partial` — some components failed, results may be incomplete                                                                                                                                                                                        |
| **400** | Invalid request — missing or conflicting `xml` / `record_json`, empty required fields                                                                                                                                                                |
| **422** | Validation error — payload does not reconcile with the record (includes court search ID / offense ID count mismatch when `cases[]` is non-empty). XML-Transformer returns **422** internally; `/evaluate` embeds it in a **500** / `failed` response |
| **500** | `failed` — processing failed entirely (orchestrator response when XML-Transformer rejects count mismatch or other XT failures)                                                                                                                       |

***

## `record_json` reference

`record_json` is **mapped-tier criminal data** — court searches, cases, and charges from a prior pipeline output. It does **not** include candidate demographics or order metadata; send `candidate_info`, `order_id`, and `order_number` via sibling fields on `record`.

**Required top-level key:** `criminal_search_results` (non-empty array)

Each `criminal_search_results[]` item must include required fields (`court_search_id`, `search_date`, `criminal_records`) — values must not be `null`, missing, or empty (`""`). `search_jurisdiction` is **optional** (ignored for validation when missing or empty; still accepted when provided). Nested `criminal_records[]` items must include **all** required case fields. Arrays (`criminal_records`, `offenses`) must not be empty. Prefer case-level `jurisdiction_state` (and `jurisdiction` when available) for location.

**Forbidden inside `record_json`:** `candidate_info`, `submission_type` (including `record_json.search.submission_type` on input), `order_id`, `order_number` (including inside `record_json.search`), `status` (including `record_json.search.status` on input).

| Field                     | Required | Example values                                                            |
| ------------------------- | -------- | ------------------------------------------------------------------------- |
| `criminal_search_results` | Yes      | Non-empty array of court search objects                                   |
| `search`                  | No       | Optional search metadata — see [`record_json.search`](#record_jsonsearch) |
| `tenant_id`               | No       | `"UBS"`, `"T001"`                                                         |

### `record_json.search`

Optional metadata in `record_json.search`. Do **not** include `submission_type`, `order_id`, `order_number`, or `status` here on input — send `record.submission_type`, `record.order_id`, and `record.order_number` instead.

| Field           | Example values                                     |
| --------------- | -------------------------------------------------- |
| `search_id`     | `"99908723"` — should align with outer `search_id` |
| `vendor_source` | `"DataDivers"`                                     |
| `search_type`   | `"USA Criminal Offender"`                          |

### `criminal_search_results[]`

| Field                 | Required | Example values                                                                                                                                                                                                |
| --------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `court_search_id`     | Yes      | `"CS40929433A"`, `"78901234"`                                                                                                                                                                                 |
| `search_date`         | Yes      | `"2026-06-16"`                                                                                                                                                                                                |
| `criminal_records`    | Yes      | Non-empty array of case objects                                                                                                                                                                               |
| `search_jurisdiction` | No       | `"NC-MECKLENBURG"`, `"TX-HARRIS"` — optional; ignored for validation when missing or empty; accepted when provided (e.g. enrichment / federal fallback). Prefer case-level `jurisdiction_state` for location. |

### `criminal_records[]` (case)

| Field                | Required | Example values                       |
| -------------------- | -------- | ------------------------------------ |
| `case_number`        | Yes      | `"2009D 008764"`, `"2024-CF-00312"`  |
| `jurisdiction_state` | Yes      | `"NC"`, `"TX"`, `"CA"`               |
| `file_date`          | Yes      | `"2009-02-10"`                       |
| `subject_name`       | Yes      | `"JOHN A DOE"`                       |
| `sub_first_name`     | Yes      | `"John"`                             |
| `sub_middle_name`    | Yes      | `"A"`                                |
| `sub_last_name`      | Yes      | `"Doe"`                              |
| `subject_dob`        | Yes      | `"2000-01-01"`                       |
| `subject_ssn`        | Yes      | `"123-45-6789"`                      |
| `subject_address`    | Yes      | `"123 Main St, Charlotte, NC 28202"` |
| `offenses`           | Yes      | Non-empty array of offense objects   |

### `offenses[]` (charge)

| Field                | Meaning                                  | Example values                                                           |
| -------------------- | ---------------------------------------- | ------------------------------------------------------------------------ |
| `offense_id`         | Charge identifier (required for routing) | `"48870"`, `"4628347"`                                                   |
| `charge`             | Charge description                       | `"Speeding"`, `"THEFT OF PROPERTY >= $2,500"`                            |
| `disposition`        | Case outcome                             | `"Guilty"`, `"Dismissed"`, `"Pending"`, `"Deferred"`, `"No Information"` |
| `type`               | Offense severity                         | `"Misdemeanor"`, `"Felony"`, `"M"`, `"FELONY"`, `"D"`                    |
| `charge_date`        | Charge date                              | `"2009-02-10"`, `null`                                                   |
| `disposition_date`   | Disposition date                         | `"2009-02-10"`, `null`                                                   |
| `arrest_date`        | Arrest date                              | `"2009-02-08"`, `null`                                                   |
| `pending_court_date` | Next court date when pending             | `"2026-07-15"`, `null`                                                   |
| `sentence`           | **Optional** structured sentence object  | See [`sentence`](#sentence-on-offense) — omit if not applicable          |

### `sentence` (on offense)

**Optional** — omit the entire `sentence` block when sentence data is not available or not yet in scope. When provided, individual fields within `sentence` are also optional unless noted otherwise.

| Field                | Meaning                                                | Example values                                |
| -------------------- | ------------------------------------------------------ | --------------------------------------------- |
| `incarceration_date` | Date incarceration began                               | `"2009-02-15"`, `null` (`YYYY-MM-DD` or null) |
| `release_date`       | Date released from custody                             | `"2010-01-15"`, `null` (`YYYY-MM-DD` or null) |
| `is_serving`         | Whether the subject is currently serving this sentence | `true`, `false`                               |
| `probation`          | Probation term                                         | `"12 months"`, `null`                         |
| `jail_time`          | Jail sentence                                          | `"30 days"`, `null`                           |
| `prison_time`        | Prison sentence                                        | `"2 years"`, `null`                           |
| `suspended`          | Suspended sentence                                     | `"6 months suspended"`, `null`                |
| `restitution`        | Restitution amount                                     | `"500.00"`, `null`                            |
| `community_service`  | Community service hours                                | `"40 hours"`, `null`                          |
| `fines`              | Fine amount                                            | `"250.00"`, `null`                            |
| `court_costs`        | Court costs                                            | `"100.00"`, `null`                            |
| `sentence_comments`  | Free-text notes                                        | `"Credit for time served"`, `null`            |

<Note>
  Send `candidate_info` as a flat block with a single-line `address` string and `submission_type` as a top-level field on `record`. The API handles normalization internally — you do not need to send structured `addresses[]` or nest these fields inside `record_json`.
</Note>

***

## Code Examples

<Accordion title="Resubmission — mapped JSON (record_json)">
  Primary example for JSON resubmission:

  ```json theme={null}
  {
    "record": {
      "search_id": "99908723",
      "search_date": "2026-06-16",
      "order_id": "789",
      "order_number": "789.1",
      "submission_type": "resubmit",
      "cases": [
        { "court_search_id": "CS40929433A", "offense_id": "34789896" }
      ],
      "record_json": {
        "criminal_search_results": [{
          "court_search_id": "CS40929433A",
          "search_date": "2026-06-16",
          "criminal_records": [{
            "case_number": "2009D 008764",
            "jurisdiction_state": "NC",
            "file_date": "2009-02-10",
            "subject_name": "JOHN A DOE",
            "sub_first_name": "John",
            "sub_middle_name": "A",
            "sub_last_name": "Doe",
            "subject_dob": "2000-01-01",
            "subject_ssn": "123-45-6789",
            "subject_address": "123 Main St, Charlotte, NC 28202",
            "offenses": [{
              "offense_id": "34789896",
              "type": "Misdemeanor",
              "charge": "Speeding",
              "disposition": "Guilty",
              "charge_date": "2009-02-10",
              "disposition_date": "2009-02-10",
              "pending_court_date": null,
              "sentence": {
                "incarceration_date": "2024-06-01",
                "release_date": "2026-06-01",
                "is_serving": true,
                "probation": null,
                "jail_time": "30 days",
                "prison_time": null,
                "fines": "250.00",
                "court_costs": "100.00"
              }
            }]
          }]
        }]
      },
      "candidate_info": {
        "first_name": "John",
        "last_name": "Doe",
        "date_of_birth": "2000-01-01",
        "address": "123 Main St, Austin, TX 78701"
      }
    }
  }
  ```

  <Warning>
    Do not nest `candidate_info`, `submission_type`, `order_id`, or `order_number` inside `record_json`. Send `submission_type: "resubmit"` as a top-level field on `record` whenever `record_json` is provided.
  </Warning>
</Accordion>

<Accordion title="Resubmission — pending charge (offense snippet)">
  Example offense with `Pending` disposition and a future `pending_court_date`:

  ```json theme={null}
  {
    "offense_id": "48871",
    "type": "Misdemeanor",
    "charge": "Driving While License Revoked",
    "disposition": "Pending",
    "charge_date": "2024-03-15",
    "disposition_date": null,
    "pending_court_date": "2026-07-15",
    "sentence": {
      "incarceration_date": null,
      "release_date": null,
      "is_serving": false,
      "probation": null,
      "jail_time": null,
      "prison_time": null,
      "suspended": null,
      "restitution": null,
      "community_service": null,
      "fines": null,
      "court_costs": null,
      "sentence_comments": null
    }
  }
  ```
</Accordion>

<Accordion title="Initial submission — raw XML">
  Same envelope as resubmission — use `xml` instead of `record_json` + `submission_type`. Omit `submission_type` on initial submissions:

  ```json theme={null}
  {
    "record": {
      "search_id": "99908723",
      "search_date": "2026-06-16",
      "order_id": "789",
      "order_number": "789.1",
      "xml": "<ScreeningResults>...</ScreeningResults>",
      "cases": [
        { "court_search_id": "CS40929433A", "offense_id": "34789896" }
      ],
      "candidate_info": {
        "first_name": "John",
        "last_name": "Doe",
        "date_of_birth": "2000-01-01",
        "address": "123 Main St, Austin, TX 78701"
      }
    }
  }
  ```
</Accordion>

<Accordion title="Full Request Body — XML with candidate overrides">
  Copy and modify this JSON payload for initial XML submissions:

  ```json theme={null}
  {
    "record": {
      "xml": "<ScreeningResults>...</ScreeningResults>",
      "search_id": "76190bc0-e58c-494f-92c5-0c9a47389528",
      "search_date": "2025-04-30",
      "order_id": "41115779",
      "order_number": "41115779.1",
      "applicant_state": "CA",
      "candidate_info": {
        "first_name": "John",
        "middle_name": "A",
        "last_name": "Doe",
        "date_of_birth": "1990-05-15",
        "ssn": "123-45-6789",
        "address": "123 Main St, Los Angeles, CA 90001"
      },
      "cases": [
        { "court_search_id": "CS40929433A", "offense_id": "34789896" }
      ],
      "search_type": "Statewide_criminal"
    }
  }
  ```
</Accordion>

<Accordion title="Example Request (cURL / Python / JavaScript)">
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X POST "https://cra.pr.snh-ai.com/evaluate" \
      -H "Authorization: Bearer YOUR_JWT_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "record": {
          "xml": "<ScreeningResults><login><account>05022022</account><username>test</username></login><postResults order=\"12345\"><case><case_number>MJ-05003-TR-0006023-2020</case_number><offense_date>20200310</offense_date><jurisdiction_state>PA</jurisdiction_state><chargeinfo><charge>DRIVING WHILE OPERATOR PRIVILEGES SUSPENDED</charge><disposition>GUILTY</disposition></chargeinfo></case></postResults></ScreeningResults>",
          "search_id": "76190bc0-e58c-494f-92c5-0c9a47389528",
          "search_date": "2025-04-30",
          "order_id": "41115779",
          "order_number": "41115779.1",
          "applicant_state": "CA",
          "candidate_info": {
            "first_name": "John",
            "middle_name": "A",
            "last_name": "Doe",
            "date_of_birth": "1990-05-15",
            "ssn": "123-45-6789",
            "address": "123 Main St, Los Angeles, CA 90001"
          },
          "cases": [
            { "court_search_id": "CS40929433A", "offense_id": "34789896" }
          ],
          "search_type": "Statewide_criminal"
        }
      }'
    ```

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

    response = requests.post(
        "https://cra.pr.snh-ai.com/evaluate",
        headers={
            "Authorization": "Bearer YOUR_JWT_TOKEN",
            "Content-Type": "application/json"
        },
        json={
            "record": {
                "xml": "<ScreeningResults>...</ScreeningResults>",
                "search_id": "76190bc0-e58c-494f-92c5-0c9a47389528",
                "search_date": "2025-04-30",
                "order_id": "41115779",
                "order_number": "41115779.1",
                "applicant_state": "CA",
                "candidate_info": {
                    "first_name": "John",
                    "middle_name": "A",
                    "last_name": "Doe",
                    "date_of_birth": "1990-05-15",
                    "ssn": "123-45-6789",
                    "address": "123 Main St, Los Angeles, CA 90001"
                },
                "cases": [
                    { "court_search_id": "CS40929433A", "offense_id": "34789896" }
                ],
                "search_type": "Statewide_criminal"
            }
        }
    )
    result = response.json()
    decision = result["data"]["decision"]
    print(f"Search queue: {decision['search_queue']}")
    for court in decision["court_decisions"]:
        for case in court["case_decisions"]:
            for offense in case["offenses"]:
                print(offense["offense_id"], offense["charge_decision"], offense["routing"]["queue"])
    ```

    ```javascript JavaScript theme={null}
    const response = await fetch("https://cra.pr.snh-ai.com/evaluate", {
      method: "POST",
      headers: {
        "Authorization": "Bearer YOUR_JWT_TOKEN",
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        record: {
          xml: "<ScreeningResults>...</ScreeningResults>",
          search_id: "76190bc0-e58c-494f-92c5-0c9a47389528",
          search_date: "2025-04-30",
          order_id: "41115779",
          order_number: "41115779.1",
          applicant_state: "CA",
          candidate_info: {
            first_name: "John",
            middle_name: "A",
            last_name: "Doe",
            date_of_birth: "1990-05-15",
            ssn: "123-45-6789",
            address: "123 Main St, Los Angeles, CA 90001"
          },
          cases: [
            { court_search_id: "CS40929433A", offense_id: "34789896" }
          ],
          search_type: "Statewide_criminal"
        }
      })
    });
    const result = await response.json();
    const decision = result.data.decision;
    console.log(`Search queue: ${decision.search_queue}`);
    for (const court of decision.court_decisions) {
      for (const c of court.case_decisions) {
        for (const offense of c.offenses) {
          console.log(offense.offense_id, offense.charge_decision, offense.routing.queue);
        }
      }
    }
    ```
  </CodeGroup>
</Accordion>

<Accordion title="Example Response — Success (200)">
  ```json theme={null}
  {
    "success": true,
    "data": {
      "search_id": "42198501",
      "correlation_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "status": "success",
      "errors": [],
      "decision": {
        "search_id": "42198501",
        "decision_date": "2026-05-19T14:22:10Z",
        "record_decision": "REPORTABLE",
        "search_queue": "Auditor",
        "order_id": "41115779",
        "order_number": "41115779.1",
        "ubs_search_type": "Statewide_criminal",
        "court_decisions": [
          {
            "court_search_id": "TBD",
            "court_decision": "REPORTABLE",
            "court_queue": "Auditor",
            "case_decisions": [
              {
                "case_number": "2024-CF-00312",
                "case_decision": "REPORTABLE",
                "case_queue": "Auditor",
                "jurisdiction_state": "TX",
                "id_match": {
                  "is_match": true,
                  "match_score": 0.925,
                  "name_score": 1,
                  "dob_score": 1,
                  "ssn_score": 0,
                  "address_score": 1,
                  "details": "Name/Alias Score: 100 % (Weight: 45 %)\nDOB Score: 100 % (Weight: 40 %)\nSSN Score: 0 % (Weight: 10 %)\nAddress Score: 100 % (Weight: 5 %)\nOverall Score: 92.5 % (Qualify: 85 %)"
                },
                "offenses": [
                  {
                    "offense_id": "40112987",
                    "charge": "THEFT OF PROPERTY >= $2,500",
                    "charge_decision": "REPORTABLE",
                    "rationale": "Conviction within 7-year lookback period. Felony conviction is reportable under FCRA and Texas state law.",
                    "type": "FELONY",
                    "disposition": "GUILTY",
                    "needs_human_review": false,
                    "cited_rules": [
                      "FCRA_CONV_NO_FEDERAL_LIMIT",
                      "TX_CONV_NO_STATE_LIMIT"
                    ],
                    "citations": [
                      "15 U.S.C. § 1681c(a)(2) - No federal time limit on criminal convictions",
                      "Tex. Bus. & Com. Code § 20.05 - Texas has no state-specific conviction limit"
                    ],
                    "routing": {
                      "queue": "Auditor",
                      "reportability": "Reportable",
                      "is_automatable": false,
                      "identity_level": "High",
                      "identity_score": 0.925,
                      "identity_insufficient": false,
                      "reportability_insufficient": false
                    }
                  }
                ]
              }
            ]
          }
        ]
      },
      "timing": {
        "xml_transformer_ms": 1200.5,
        "compliance_ms": 330.7,
        "total_ms": 1850.3
      }
    },
    "meta": {
      "api_version": "2.0.0",
      "process_time_ms": 1855,
      "correlation_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
    }
  }
  ```

  <Note>
    When the source data is clean, the `validation` field is **omitted entirely** from the response (not returned as an empty object). It only appears when there are real data-quality issues — see the next example.
  </Note>
</Accordion>

<Accordion title="Example Response — With Validation Issues (200)">
  When the source data has quality issues, the response includes a structured `validation` object alongside the `decision`. The `validation` field is only present when issues exist:

  ```json theme={null}
  {
    "success": true,
    "data": {
      "search_id": "40851153",
      "correlation_id": "966f69b2-2855-4a67-b4f3-cd8b3da053c7",
      "status": "success",
      "errors": [],
      "validation": {
        "status": "needs_manual_review",
        "issues": {
          "errors": [
            {
              "level": "candidate",
              "field": "subject_ssn",
              "issue": "SSN is missing or could not be determined.",
              "description": "SSN is required to confirm identity at the highest tier.",
              "case_id": "",
              "case_number": ""
            },
            {
              "level": "offense",
              "field": "disposition",
              "issue": "Disposition is missing or could not be determined.",
              "description": "Disposition drives reportability, so missing values force the charge to Insufficient Data.",
              "case_id": "case-1",
              "case_number": "J-1401-TR-200304452",
              "offense_id": "34714104"
            }
          ],
          "warnings": [
            {
              "level": "offense",
              "field": "type_class",
              "issue": "Type class mismatch: rule='MISDEMEANOR', llm/cache='Other'",
              "description": "The rule-engine classification takes precedence; this is informational.",
              "case_id": "case-1",
              "case_number": "J-1401-TR-200304452",
              "offense_id": "34714104"
            }
          ]
        }
      },
      "decision": {
        "search_id": "40851153",
        "decision_date": "2026-03-31T20:22:36Z",
        "search_queue": "Insufficient Data",
        "order_id": "",
        "court_decisions": [ "..." ]
      },
      "timing": {
        "xml_transformer_ms": 22095.3,
        "compliance_ms": 330.7,
        "total_ms": 30016.1
      }
    },
    "meta": {
      "api_version": "2.0.0",
      "process_time_ms": 30020,
      "correlation_id": "966f69b2-2855-4a67-b4f3-cd8b3da053c7"
    }
  }
  ```

  <Note>
    **Validation issue types:**

    * **`issues.errors`** — Hard data-quality problems (missing SSN, missing disposition, indeterminate charge classification). These typically force affected charges into the `Insufficient Data` queue, which then propagates up through `case_queue`, `court_queue`, and `search_queue`.
    * **`issues.warnings`** — Soft mismatches (e.g., type-class or disposition-class disagreements between the rule engine and the ML model). The rule-engine classification takes precedence — these are informational only and do not change routing on their own.
  </Note>
</Accordion>

<Accordion title="Example Response — Partial (206)">
  When some components fail, `status` is `partial` and `degradation` is included. The `errors` array carries the engine-level failure messages:

  ```json theme={null}
  {
    "success": true,
    "data": {
      "search_id": "42198503",
      "correlation_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
      "status": "partial",
      "errors": [
        "Compliance-Engine failed: read timeout after 30s"
      ],
      "decision": {
        "search_id": "42198503",
        "decision_date": "2026-05-22T01:00:00Z",
        "search_queue": "Auditor",
        "order_id": "",
        "court_decisions": [ "..." ]
      },
      "degradation": {
        "xml_transformer": { "status": "available", "error": null },
        "compliance_engine": { "status": "degraded", "error": "read timeout after 30s" }
      },
      "timing": {
        "xml_transformer_ms": 1500.2,
        "compliance_ms": 400.1,
        "total_ms": 2100.5
      }
    },
    "meta": {
      "api_version": "2.0.0",
      "process_time_ms": 2105,
      "correlation_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901"
    }
  }
  ```
</Accordion>

<Accordion title="Example Response — Failed (court search ID / offense ID count mismatch)">
  When court search ID / offense ID counts in `cases[]` do not match the parsed XML/record\_json, XML-Transformer rejects the request internally (HTTP **422**, `error_code: CASE_OFFENSE_COUNT_MISMATCH`). The orchestrator returns HTTP **500** with `data.status: "failed"` and no compliance evaluation:

  ```json theme={null}
  {
    "success": false,
    "data": {
      "search_id": "76190bc0-e58c-494f-92c5-0c9a47389528",
      "correlation_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "status": "failed",
      "errors": [
        "XML-Transformer HTTP error 422: Court search ID and offense ID counts do not match the XML: 17 court search ID(s) in the request but 24 case(s) in the XML; 22 offense ID(s) in the request but 32 offense(s) in the XML. Each court search ID must map to exactly one XML case and each offense ID to exactly one XML offense; resubmit with the complete set."
      ],
      "degradation": {
        "xml_transformer": {
          "status": "failed",
          "error": "Court search ID and offense ID counts do not match the XML: 17 court search ID(s) in the request but 24 case(s) in the XML; 22 offense ID(s) in the request but 32 offense(s) in the XML. Each court search ID must map to exactly one XML case and each offense ID to exactly one XML offense; resubmit with the complete set."
        },
        "compliance_engine": { "status": "not_called", "error": null }
      },
      "timing": {
        "xml_transformer_ms": 120.5,
        "compliance_ms": 0,
        "total_ms": 125.0
      }
    },
    "meta": {
      "api_version": "2.0.0",
      "process_time_ms": 130,
      "correlation_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
    }
  }
  ```

  XML-Transformer response on `/transform-xml` (HTTP **422**):

  ```json theme={null}
  {
    "status": "error",
    "error_code": "CASE_OFFENSE_COUNT_MISMATCH",
    "message": "Court search ID and offense ID counts do not match the XML: 1 court search ID(s) in the request but 2 case(s) in the XML; 1 offense ID(s) in the request but 3 offense(s) in the XML. Each court search ID must map to exactly one XML case and each offense ID to exactly one XML offense; resubmit with the complete set.",
    "search_id": "99916916",
    "details": {
      "court_search_id_count": 1,
      "xml_case_count": 2,
      "offense_id_count": 1,
      "xml_offense_count": 3,
      "court_search_id_matches_cases": false,
      "offense_id_matches_offenses": false
    }
  }
  ```

  | Field                                   | Example                                               |
  | --------------------------------------- | ----------------------------------------------------- |
  | `error_code`                            | `CASE_OFFENSE_COUNT_MISMATCH`                         |
  | `http_status`                           | `422`                                                 |
  | `details.court_search_id_count`         | `17` (evaluate example) / `1` (transform-xml example) |
  | `details.xml_case_count`                | `24` / `2`                                            |
  | `details.offense_id_count`              | `22` / `1`                                            |
  | `details.xml_offense_count`             | `32` / `3`                                            |
  | `details.court_search_id_matches_cases` | `false`                                               |
  | `details.offense_id_matches_offenses`   | `false`                                               |
</Accordion>

***

## Validation

The `validation` object surfaces **data-quality issues** in the source record. It is **omitted entirely from the response when the source data is clean** — your client should treat the absence of `validation` as "no issues".

When present, it has this shape:

```json theme={null}
{
  "validation": {
    "status": "needs_manual_review",
    "issues": {
      "errors":   [ { "level": "...", "field": "...", "issue": "...", "description": "...", "case_id": "...", "case_number": "...", "offense_id": "..." } ],
      "warnings": [ { "level": "...", "field": "...", "issue": "...", "description": "...", "case_id": "...", "case_number": "...", "offense_id": "..." } ]
    }
  }
}
```

Each item:

| Field         | Type   | Description                                                                                               |
| ------------- | ------ | --------------------------------------------------------------------------------------------------------- |
| `level`       | string | Where the issue was found: `candidate`, `case_subject`, `case`, or `offense`                              |
| `field`       | string | The specific field that failed. Examples: `subject_ssn`, `disposition`, `disposition_class`, `type_class` |
| `issue`       | string | Short human-readable description                                                                          |
| `description` | string | Longer explanation of why the field matters                                                               |
| `case_id`     | string | Internal case identifier (empty for candidate-level issues)                                               |
| `case_number` | string | Associated case number (empty for candidate-level issues)                                                 |
| `offense_id`  | string | Offense identifier (only on offense-level items)                                                          |

<Note>
  **`errors` vs `warnings`:**

  * **`errors`** are hard data-quality problems — they typically force affected charges into the `Insufficient Data` queue, which then bubbles up through `case_queue` → `court_queue` → `search_queue`.
  * **`warnings`** are soft mismatches (e.g., the rule engine and ML model disagree on type class). The rule-engine classification wins; these are informational only.

  `errors` here is **not** the same as the top-level `data.errors` array. `data.errors` carries **service-level** failures (engine timeouts, network errors). `validation.issues.errors` carries **data-level** problems in the input record.
</Note>

***

## JSON Field Reference

Field values below are sourced from the live API contract. See also [Glossary](/glossary) and [Routing](/api-reference/routing).

### Request — outer `record`

| Field                          | Example values                                                                                                                                  | Notes                                                                                                                                           |
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `search_id`                    | `"99908723"`, `"76190bc0-e58c-494f-92c5-0c9a47389528"`                                                                                          | Required — any non-empty string                                                                                                                 |
| `search_date`                  | `"2026-06-16"`, `"2024-01-15"`                                                                                                                  | Required — `YYYY-MM-DD`                                                                                                                         |
| `order_id`                     | `"789"`, `"41115779"`                                                                                                                           | Required — non-empty; top-level on `record` only; echoed in response                                                                            |
| `order_number`                 | `"789.1"`, `"41115779.1"`                                                                                                                       | Required — non-empty; UBS format `orderID.subOrderID`; top-level on `record` only                                                               |
| `xml`                          | `"<ScreeningResults>...</ScreeningResults>"`                                                                                                    | Initial submission — mutually exclusive with `record_json`                                                                                      |
| `submission_type`              | `"resubmit"`                                                                                                                                    | Required when `record_json` is present — top-level on `record` only                                                                             |
| `record_json`                  | `{ "criminal_search_results": [...] }`                                                                                                          | Resubmission — must **not** contain `candidate_info`, `submission_type`, `order_id`, or `order_number`                                          |
| `candidate_info`               | `{ "first_name": "John", "last_name": "Doe", "date_of_birth": "2000-01-01", "address": "123 Main St, Austin, TX 78701" }`                       | Required — non-empty; sibling on `record` for **both** XML and JSON paths — flat shape only                                                     |
| `candidate_info.first_name`    | `"John"`                                                                                                                                        | Required — non-empty                                                                                                                            |
| `candidate_info.last_name`     | `"Doe"`                                                                                                                                         | Required — non-empty                                                                                                                            |
| `candidate_info.date_of_birth` | `"2000-01-01"`                                                                                                                                  | Required — non-empty; `YYYY-MM-DD`                                                                                                              |
| `candidate_info.ssn`           | `"123-45-6789"`                                                                                                                                 | Any format (normalized internally)                                                                                                              |
| `candidate_info.address`       | `"123 Main St, Austin, TX 78701"`                                                                                                               | Single-line string — not `addresses[]`                                                                                                          |
| `tenant_id`                    | `"UBS"`, `"T001"`                                                                                                                               | Optional                                                                                                                                        |
| `applicant_state`              | `"CA"`, `"TX"`, `"NC"`                                                                                                                          | Optional — US state code                                                                                                                        |
| `cases`                        | See matching example below                                                                                                                      | **Optional** — omit or `[]` to evaluate all. When non-empty, must map **every** court and offense (exact count match); partial subsets rejected |
| `cases[]` (matching)           | `[{ "court_search_id": "A", "offense_id": "1" }, { "court_search_id": "A", "offense_id": "2" }, { "court_search_id": "B", "offense_id": "3" }]` | 2 courts, 3 offenses — same counts as XML                                                                                                       |
| `cases[].court_search_id`      | `"CS40929433A"`, `"A"`                                                                                                                          | One row per offense; same ID may repeat across rows                                                                                             |
| `cases[].offense_id`           | `"34789896"`, `"1"`                                                                                                                             | Total rows = total offenses in record                                                                                                           |
| `cases[].is_excluded`          | `true`, `false`                                                                                                                                 | Optional — defaults to `false`. When `true`, charge is excluded from response but still processed                                               |
| `offense_ids`                  | `["4628347", "48870"]`                                                                                                                          | Deprecated — use `cases`                                                                                                                        |
| `search_type`                  | `"Statewide_criminal"`, `"County_criminal"`                                                                                                     | Optional                                                                                                                                        |

### Response — envelope

| Field                       | Example values                                                                                                                                                      | Notes                                                                                         |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| `success`                   | `true`, `false`                                                                                                                                                     | `true` for HTTP 200/206 when processing succeeded or partial                                  |
| `data.status`               | `"success"`, `"partial"`, `"failed"`                                                                                                                                | Processing outcome                                                                            |
| `data.errors`               | `[]`, `["Compliance-Engine failed: read timeout after 30s"]`, `["XML-Transformer HTTP error 422: Court search ID and offense ID counts do not match the XML: ..."]` | Service-level failures (timeouts, engine errors, court search ID / offense ID count mismatch) |
| `data.validation.status`    | `"needs_manual_review"`                                                                                                                                             | Only present when data-quality issues exist; omitted when clean                               |
| `data.timing.total_ms`      | `2121`, `1850.3`                                                                                                                                                    | Processing time in ms                                                                         |
| `data.degradation.*.status` | `"available"`, `"degraded"`, `"unavailable"`, `"failed"`, `"not_called"`                                                                                            | On `partial`/`failed` — e.g. `compliance_engine: not_called` when XT fails on cases\[]        |
| `meta.api_version`          | `"2.0.0"`                                                                                                                                                           | API version                                                                                   |
| `meta.correlation_id`       | `"a1b2c3d4-e5f6-7890-abcd-ef1234567890"`                                                                                                                            | Request trace ID                                                                              |

HTTP status: `200` success · `206` partial · `400` bad input · `422` validation · `500` failed

### Response — decision labels (`*_decision`)

| Field                              | Example values                                  | Notes                                |
| ---------------------------------- | ----------------------------------------------- | ------------------------------------ |
| `offenses[].charge_decision`       | `REPORTABLE`, `NOT_REPORTABLE`, `MANUAL_REVIEW` | Per-offense — from compliance engine |
| `case_decisions[].case_decision`   | `REPORTABLE`, `NOT_REPORTABLE`, `MANUAL_REVIEW` | Derived from `case_queue`            |
| `court_decisions[].court_decision` | `REPORTABLE`, `NOT_REPORTABLE`, `MANUAL_REVIEW` | Derived from `court_queue`           |
| `record_decision`                  | `REPORTABLE`, `NOT_REPORTABLE`, `MANUAL_REVIEW` | Derived from `search_queue`          |

**Queue → decision mapping** (aggregated levels only):

| `*_queue` value     | `*_decision` value |
| ------------------- | ------------------ |
| `Automation`        | `NOT_REPORTABLE`   |
| `Auditor`           | `REPORTABLE`       |
| `Insufficient Data` | `MANUAL_REVIEW`    |

### Response — routing queues (`*_queue`)

| Field                                                        | Example values                               | Rollup precedence                              |
| ------------------------------------------------------------ | -------------------------------------------- | ---------------------------------------------- |
| `search_queue`, `court_queue`, `case_queue`, `routing.queue` | `Automation`, `Auditor`, `Insufficient Data` | `Insufficient Data` > `Auditor` > `Automation` |

### Response — per-offense routing

| Field                                | Example values                                      | Notes                                                                                             |
| ------------------------------------ | --------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `routing.reportability`              | `Reportable`, `Not Reportable`, `Not Enough Info`   | Human-readable reportability                                                                      |
| `routing.identity_level`             | `High`, `Medium`, `Not Enough Info`, `Not Matching` | From `identity_score`: ≥ 0.9 → High; 0.7–0.89 → Medium; ≤ −1 → Not Matching; else Not Enough Info |
| `routing.identity_score`             | `0.925`, `0.85`, `0.40`                             | 0–1                                                                                               |
| `routing.is_automatable`             | `true`, `false`                                     | `true` only when queue is `Automation`                                                            |
| `routing.identity_insufficient`      | `true`, `false`                                     | Identity data too sparse                                                                          |
| `routing.reportability_insufficient` | `true`, `false`                                     | Offense data too sparse                                                                           |
| `routing.third_id_required`          | `true`, `false`                                     | Third identity factor required                                                                    |
| `needs_human_review`                 | `true`, `false`                                     | `true` when `charge_decision` is `MANUAL_REVIEW`                                                  |

### Response — `id_match` (per case)

| Field                                  | Example values                                | Notes                            |
| -------------------------------------- | --------------------------------------------- | -------------------------------- |
| `is_match`                             | `true`, `false`                               | From identity-match service      |
| `match_score`                          | `0.85`, `0.925`, `0.40`                       | Overall weighted score 0–1       |
| `name_score`, `dob_score`, `ssn_score` | `0`–`1` typically                             | Component scores                 |
| `address_score`                        | `1`, `0`, `-1`                                | `-1` when address was not scored |
| `details`                              | `"Name/Alias Score: 100 % ... Qualify: 70 %"` | Human-readable breakdown         |

### Response — other offense fields

| Field                 | Example values                                             | Notes                              |
| --------------------- | ---------------------------------------------------------- | ---------------------------------- |
| `offense_id`          | `"48870"`, `"40112987"`                                    | Charge identifier from source data |
| `charge`              | `"Speeding"`, `"THEFT OF PROPERTY >= $2,500"`              | Free text                          |
| `type`, `disposition` | Echoed from the input record                               | Free text — see request examples   |
| `cited_rules`         | `["GENERAL_TRAFFIC_NON_REPORT", "TX_CONV_NO_STATE_LIMIT"]` | Rule IDs — not a fixed enum        |
| `citations`           | `["15 U.S.C. § 1681c(a)(5)"]`, `[]`                        | Statute refs — often empty         |
| `court_search_id`     | `"111111"`, `"TBD"`                                        | Vendor passthrough                 |
| `case_number`         | `"2009D 008764"`                                           | Court case number                  |

### Request — offense `sentence` (optional)

Omit `sentence` entirely when not applicable. When included, all fields below are optional.

| Field                | Example values         | Notes                                                  |
| -------------------- | ---------------------- | ------------------------------------------------------ |
| `incarceration_date` | `"2009-02-15"`, `null` | `YYYY-MM-DD` or null                                   |
| `release_date`       | `"2010-01-15"`, `null` | `YYYY-MM-DD` or null                                   |
| `is_serving`         | `true`, `false`        | Whether the subject is currently serving this sentence |

### Response — validation (when issues exist)

| Field                   | Example values                                                  | Notes                      |
| ----------------------- | --------------------------------------------------------------- | -------------------------- |
| `validation.status`     | `"needs_manual_review"`                                         | Omitted when data is clean |
| `issues.errors[].level` | `candidate`, `case_subject`, `case`, `offense`                  | Where the issue was found  |
| `issues.errors[].field` | `subject_ssn`, `disposition`, `disposition_class`, `type_class` | Field that failed          |

<Note>
  **`record_decision` vs `search_queue`:** These are linked at aggregated levels — e.g. `search_queue: "Automation"` always pairs with `record_decision: "NOT_REPORTABLE"`. Per-offense `charge_decision` comes from the compliance engine independently; aggregated `*_decision` labels mirror the queue mapping above.
</Note>

***

## Decision Hierarchy

The evaluation result (`data.decision`) is structured hierarchically: **Record → Court → Case → Offense**

Each level carries both a reportability label (`*_decision`) and a routing queue (`*_queue`):

```
record_decision + search_queue          (record level)
└── court_decisions[]
    ├── court_decision + court_queue    (court level)
    └── case_decisions[]
        ├── case_decision + case_queue  (case level)
        ├── id_match                    (identity match scores)
        └── offenses[]
            ├── charge_decision         (REPORTABLE | NOT_REPORTABLE | MANUAL_REVIEW)
            ├── rationale
            ├── cited_rules
            ├── citations
            └── routing                 (queue, reportability, identity_level, ...)
```

### Aggregated decision labels (`*_decision`)

At case, court, and record levels, `*_decision` is **derived from the matching `*_queue`**, not rolled up from child `charge_decision` values:

| `*_queue`           | `*_decision`     |
| ------------------- | ---------------- |
| `Automation`        | `NOT_REPORTABLE` |
| `Auditor`           | `REPORTABLE`     |
| `Insufficient Data` | `MANUAL_REVIEW`  |

`charge_decision` on each offense is computed independently by the compliance engine (`REPORTABLE` | `NOT_REPORTABLE` | `MANUAL_REVIEW`).

### Queue rollup (`*_queue`)

`case_queue`, `court_queue`, and `search_queue` are aggregated from per-charge `routing.queue` values:

1. **`Insufficient Data`** (highest) — if any child queue is `Insufficient Data`
2. **`Auditor`** — if any child is `Auditor` (and none are `Insufficient Data`)
3. **`Automation`** (lowest) — every child is `Automation`

<Note>
  Use per-offense `charge_decision` and `routing.queue` for charge-level handling. At aggregated levels, `*_decision` mirrors `*_queue` via the mapping above — e.g. one reportable charge routed to `Auditor` yields `record_decision: "REPORTABLE"` and `search_queue: "Auditor"`.
</Note>

***

## Related

* [Charge-Level Routing](/api-reference/routing) — Queue categories, routing matrix, and overrides
* [Authentication](/authentication) — How to get JWT tokens
* [Glossary](/glossary) — Decision values and compliance terminology
