> For the complete documentation index, see [llms.txt](https://docs.aisera.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.aisera.com/aisera-platform/tenant-setup/aisera-platform-configuration/tenant-configuration-settings/ticket-iq.md).

# Ticket IQ

Configuration settings for the TicketAI prediction engine, widget layout, and escalation notifications.

The **Settings > Configuration > TicketIQ** section controls tenant-level settings for TicketAI, the prediction engine that matches incoming tickets to KB articles and surfaces AI-driven recommendations in agent-facing IT Service Management (ITSM) integrations. The two primary settings, **Policy** and **Widget**, accept JSON configurations and control prediction behavior and widget layout respectively; the remaining settings are checkboxes and integers that adjust supporting behavior.

### Policy

| **Type**    | Text field (JSON)                   |
| ----------- | ----------------------------------- |
| **Default** | JSON object with `"enabled": false` |

Controls how TicketAI matches incoming tickets to knowledge base (KB) articles, defining query fields, preprocessing functions, matching method, confidence thresholds, score normalization, and enabled prediction types. TicketAI returns no predictions until you set `enabled` to `true` in this JSON. When the widget server processes a TicketAI request, it uses a channel-level policy first, then a bot-level policy, then this tenant-level field as the final fallback.

The default value below reflects a minimal working configuration. The full schema supports additional properties documented in the sections below.

#### Core

<table><thead><tr><th width="156.5999755859375">Property</th><th width="100.20001220703125">Type</th><th width="163">Default</th><th width="599.9968872070312">Description</th></tr></thead><tbody><tr><td><code>enabled</code></td><td>Boolean</td><td><code>false</code></td><td>Master switch for all TicketAI predictions. Set to <code>true</code> to activate ticket matching. Leaving this <code>false</code> means the widget server returns no predictions regardless of any other settings.</td></tr><tr><td><code>queryFields</code></td><td>Array of strings</td><td><code>["title", "description"]</code></td><td>The ticket fields sent to the matching engine. Adjust this if your ticketing system stores relevant content in fields other than title and description.</td></tr><tr><td><code>preProcessing</code></td><td>Object</td><td>See default</td><td>JavaScript function strings applied to each query field before the engine passes it to the matching service. Each key is a valid query field name (<code>title</code>, <code>description</code>, or <code>searchText</code>); the value is a function string with the signature <code>function(fieldValue)</code>, where <code>fieldValue</code> is the raw string from that ticket field. The function must return a string; a non-string return falls back to the original field value. The default <code>description</code> function strips a common <code>"Message: Short Description of Issue:"</code> prefix that some integrations prepend to ticket descriptions.</td></tr></tbody></table>

#### Matching and processing

<table><thead><tr><th width="281">Property</th><th width="100.39996337890625">Type</th><th width="137">Default</th><th width="507.60003662109375">Description</th></tr></thead><tbody><tr><td><code>processing.method</code></td><td>String</td><td><code>"sequential"</code></td><td>Controls how the engine queries ticket fields. <code>"sequential"</code> tries fields in the order listed in <code>processing.fields</code> and stops as soon as a result reaches <code>sequentialStopScore</code>, which is more efficient. <code>"parallel"</code> runs all fields simultaneously and merges the results, which may surface more matches at the cost of speed.</td></tr><tr><td><code>processing.fields</code></td><td>Array of strings</td><td><code>["title", "title+description"]</code></td><td>The fields or combined field expressions passed to the matching engine. <code>"title+description"</code> concatenates both fields into a single query.</td></tr><tr><td><code>processing.sequentialStopScore</code></td><td>Integer</td><td><code>90</code></td><td>Only applies when <code>method</code> is <code>"sequential"</code>. If a match reaches this confidence score, the engine stops evaluating remaining fields. Lowering this value causes the engine to query more fields before stopping; raising it requires a higher-confidence early match before it stops.</td></tr><tr><td><code>bolScoreFunc</code></td><td>String (JS function)</td><td>See default</td><td>JavaScript function string that computes the BOL (Bag of Lematta) confidence score. The function has the signature <code>function(numOfBOLKB)</code>, where <code>numOfBOLKB</code> is an integer count of BOL KB matches. The function must return a floating-point score. The default function returns a decreasing score as more matches are found, reflecting lower confidence when many articles match. </td></tr><tr><td><code>confidenceScoreThreshold</code></td><td>Object</td><td>See default</td><td>Minimum confidence score each model must produce before its results are surfaced. The engine discards results below these thresholds. Keys: <code>intent</code> (default <code>80</code>), <code>bol</code> (default <code>60</code>), <code>keywordsearch</code> (default <code>30</code>), <code>kbClassifier</code> (default <code>40</code>). Lower thresholds return more results with less confidence; higher thresholds return fewer but stronger matches.</td></tr><tr><td><code>scoreNormalization</code></td><td>Object</td><td>See default</td><td>JavaScript function strings that remap raw model scores before results are returned. Each key is a model type; valid keys are <code>intent</code>, <code>bol</code>, <code>keywordsearch</code>, <code>kb_classifier</code>, and <code>publicKBs</code>. Each value is a function string with the signature <code>function(score)</code>, where <code>score</code> is the raw numeric score from that model. The function must return a number; the return value is used directly with no clamping or validation. On exception, the original score is preserved. An absent key leaves that model's scores unchanged. <code>confidenceScoreThreshold</code> filtering runs before normalization, so only results that already passed the threshold are normalized. See <strong>Score normalization input ranges</strong> below for the expected input range per model type.</td></tr><tr><td><code>maxNumOfKB</code></td><td>Integer</td><td><code>5</code></td><td>Maximum number of KB articles returned per prediction call. Increase this if agents need more suggestions; decrease it to surface only the highest-confidence matches.</td></tr><tr><td><code>maxNumSimilarTickets</code></td><td>Integer</td><td><code>5</code></td><td>Maximum number of similar past tickets returned alongside KB predictions.</td></tr><tr><td><code>fulfillmentEnable</code></td><td>Object</td><td>Not set</td><td>Restricts which model types are allowed to contribute results. Keys: <code>intent</code>, <code>bol</code>, <code>keywordsearch</code>. Set a key to <code>false</code> to disable that model entirely for this tenant. When not set, all models run.</td></tr><tr><td><code>enableEntityMatching</code></td><td>Boolean</td><td><code>false</code></td><td>Enables entity matching validation when the tenant-server path processes requests. Adds a matching check based on entities extracted from the ticket.</td></tr><tr><td><code>isConversation</code></td><td>Boolean</td><td><code>false</code></td><td>When <code>true</code>, the engine treats the incoming ticket as a multi-turn conversation thread and adjusts its matching logic accordingly, rather than treating it as a single message.</td></tr><tr><td><code>predictionTimeout</code></td><td>Object</td><td><code>30</code> per model</td><td>Per-model request timeouts in seconds. Keys: <code>nlp</code>, <code>sentiment</code>, <code>escalation</code>. Any model not specified defaults to <code>30</code>. Increase these values if models are timing out under load.</td></tr></tbody></table>

#### Score normalization input ranges

The `score` argument each normalization function receives depends on the model type.

<table><thead><tr><th width="149">Model type</th><th width="129">Input range</th><th>Notes</th></tr></thead><tbody><tr><td><code>intent</code></td><td>0–100</td><td>Score is numeric.</td></tr><tr><td><code>bol</code></td><td>0–100</td><td>Defaults to exactly <code>98</code> when no intent-derived score is available.</td></tr><tr><td><code>keywordsearch</code></td><td>Varies</td><td>The default normalization function is calibrated for scores starting around 30. Verify the actual range for your deployment before customizing.</td></tr><tr><td><code>kb_classifier</code></td><td>Varies</td><td>The <code>kb_classifier</code> normalization key behaves differently depending on which KB Classifier mode your tenant uses. Contact Aisera before configuring a normalization function for this key as it may not produce the expected results.</td></tr><tr><td><code>publicKBs</code></td><td>Always <code>50</code></td><td>Public KB results receive a fixed score of 50 before normalization runs.</td></tr></tbody></table>

#### Prediction types

<table><thead><tr><th width="136">Property</th><th width="92">Type</th><th width="100">Default</th><th>Description</th></tr></thead><tbody><tr><td><code>predictions</code></td><td>Array of strings</td><td>Not set</td><td>The prediction types the engine runs for each request. Only types listed here are executed; omitting a type disables it entirely. Valid values: <code>"predictions"</code>, <code>"relatedKBs"</code>, <code>"similarTickets"</code>, <code>"similarRequestTickets"</code>, <code>"publicKBs"</code>, <code>"majorIncidents"</code>, <code>"sentiment"</code>, <code>"sentimentV2"</code>, <code>"sentimentV3"</code>, <code>"escalation"</code>, <code>"cannedAnswers"</code>, <code>"favoriteCannedAnswers"</code>, <code>"freeFormSearch"</code>, <code>"emotion"</code>. Limit this array to the types your use case requires to avoid unnecessary processing.</td></tr></tbody></table>

#### KB classifier

<table><thead><tr><th width="189">Property</th><th width="107">Type</th><th width="113">Default</th><th>Description</th></tr></thead><tbody><tr><td><code>useKBClassifier</code></td><td>Boolean</td><td><code>false</code></td><td>Enables the v1 KB classifier post-processor, which re-ranks KB article results after initial matching.</td></tr><tr><td><code>useKBClassifierV2</code></td><td>Boolean</td><td><code>false</code></td><td>Enables the v2 KB classifier post-processor. When both <code>useKBClassifier</code> and <code>useKBClassifierV2</code> are <code>true</code>, v2 takes precedence.</td></tr><tr><td><code>KBClassifierMode</code></td><td>String</td><td><code>"replace"</code></td><td>Controls how the classifier merges its output with existing predictions. <code>"replace"</code> discards previous KB results and substitutes the classifier's output; <code>"append"</code> adds the classifier's results to the existing list.</td></tr></tbody></table>

#### Intent extraction

<table><thead><tr><th width="186">Property</th><th width="100">Type</th><th width="99">Default</th><th>Description</th></tr></thead><tbody><tr><td><code>intentExtractorService.enabled</code></td><td>Boolean</td><td><code>false</code></td><td>Enables the intent extractor, which pulls a concise intent phrase from the ticket before passing it to prediction models.</td></tr><tr><td><code>intentExtractorService.allowInSearch</code></td><td>Boolean</td><td><code>false</code></td><td>When <code>true</code>, the intent extractor also runs during search requests, not just prediction requests.</td></tr><tr><td><code>intentExtractorService.predictions</code></td><td>Array of strings</td><td>Not set</td><td>The prediction model types that receive the extracted intent phrase as their input instead of the raw ticket fields.</td></tr></tbody></table>

#### Free-form search

<table><thead><tr><th width="260">Property</th><th width="87">Type</th><th width="100">Default</th><th>Description</th></tr></thead><tbody><tr><td><code>freeFormSearch.enabled</code></td><td>Boolean</td><td><code>false</code></td><td>Enables free-form search, which allows unstructured text queries against the knowledge base.</td></tr><tr><td><code>freeFormSearch.isSearchOnly</code></td><td>Boolean</td><td><code>false</code></td><td>When <code>true</code>, only free-form search runs for the request and all other prediction models are bypassed.</td></tr></tbody></table>

#### LLM field predictions

<table><thead><tr><th width="196">Property</th><th width="94">Type</th><th width="98">Default</th><th>Description</th></tr></thead><tbody><tr><td><code>llmFieldPredictions</code></td><td>Boolean</td><td><code>false</code></td><td>When <code>true</code>, field prediction requests are routed through the Large Language Model (LLM) prediction path instead of the standard model path.</td></tr></tbody></table>

#### Services

The following service objects enable specific AI capabilities. Each accepts an `enabled` flag, a flow or prompt identifier, and a `deadlineInSec` timeout in seconds.

<table><thead><tr><th width="166">Service</th><th width="181">Identifier property</th><th>Description</th></tr></thead><tbody><tr><td><code>fieldPredictions</code></td><td><code>promptName</code>, <code>flowId</code></td><td>Predicts values for ticket fields such as category or priority. Set <code>versionEnabled: true</code> to use the v2 prediction API path.</td></tr><tr><td><code>sentimentService</code></td><td><code>promptName</code></td><td>Analyzes the sentiment of the incoming ticket.</td></tr><tr><td><code>ragService</code></td><td><code>flowId</code></td><td>Retrieval-Augmented Generation service for knowledge-grounded responses.</td></tr><tr><td><code>summaryService</code></td><td><code>flowId</code></td><td>Generates a summary of the ticket content.</td></tr><tr><td><code>nextBestAction</code></td><td><code>flowId</code></td><td>Suggests the next best action for the agent based on the ticket.</td></tr><tr><td><code>wrapUp</code></td><td><code>flowId</code></td><td>Generates a wrap-up summary after ticket resolution.</td></tr></tbody></table>

<table><thead><tr><th width="250">Property</th><th width="83">Type</th><th width="105">Default</th><th>Description</th></tr></thead><tbody><tr><td><code>generateKnowledge</code></td><td>Object</td><td><code>{}</code></td><td>Configuration for automatic KB article generation from resolved tickets.</td></tr><tr><td><code>generatedKnowledgeTemplate</code></td><td>String</td><td><code>""</code></td><td>Template name for generated KB articles. Set to <code>"none"</code> to disable knowledge generation.</td></tr></tbody></table>

#### Phrase retriever

<table><thead><tr><th width="345">Property</th><th width="91">Type</th><th width="161">Default</th><th width="436">Description</th></tr></thead><tbody><tr><td><code>phraseRetriever.summaryTypeAttributes</code></td><td>Object</td><td><code>{"SUMMARY": {}}</code></td><td>Configuration for the phrase retriever, keyed by summary type. Each key maps to a configuration object for that summary type. Valid keys: <code>"SUMMARY"</code>, <code>"EMAIL_RESPONSE"</code>, <code>"KB_ARTICLE"</code>, <code>"ANSWER"</code>.</td></tr></tbody></table>

#### Canned answers

Canned answers are pre-written KB articles or response templates that agents can insert into a ticket reply with one click. The TicketIQ widget surfaces the most relevant ones based on the ticket's content.

**Example:** An IT agent opens a ticket — "My VPN won't connect after the Windows update." The widget surfaces:

* How to reinstall the VPN client after a Windows update
* VPN troubleshooting steps for Windows 10/11
* Reset VPN credentials

The agent clicks the most relevant article and it populates their reply field.

Canned answers differ from **relatedKBs** in purpose and audience. `relatedKBs` surfaces articles to help the agent understand the issue; `cannedAnswers` surfaces content specifically curated as ready-to-send agent replies — typically shorter, resolution-focused, and written in a directive voice.

<table data-search="false"><thead><tr><th width="279">Property</th><th width="126">Type</th><th width="96">Default</th><th width="670">Description</th></tr></thead><tbody><tr><td><code>cannedAnswer.enabled</code></td><td>Boolean</td><td><code>false</code></td><td>Enables canned answer lookups, which surface pre-written responses alongside ticket predictions.</td></tr><tr><td><code>cannedAnswer.maxCannedAnswers</code></td><td>Integer</td><td><code>10</code></td><td>Maximum number of canned answers returned per request.</td></tr><tr><td><code>cannedAnswer.botId</code></td><td>Integer</td><td><code>0</code></td><td>The bot ID used to scope canned answer lookups.</td></tr><tr><td><code>cannedAnswer.datasourceId</code></td><td>String or Array</td><td>Not set</td><td>The datasource(s) to search for canned answers. Accepts a comma-separated string of IDs or an array of integers.</td></tr><tr><td><code>cannedAnswer.useScript</code></td><td>Boolean</td><td><code>false</code></td><td>When <code>true</code>, the bot ID is determined dynamically by executing <code>script</code> rather than using the static <code>botId</code> value.</td></tr><tr><td><code>cannedAnswer.script</code></td><td>String (JS function)</td><td>Not set</td><td>JavaScript function string that determines the bot ID used for canned answer lookups. The function has the signature <code>function(input)</code>, where <code>input</code> is the ticket object. The function must return a number (integer bot ID). A non-numeric return or exception resolves to bot ID <code>0</code>. When bot ID is <code>0</code>, the system falls back to the static <code>cannedAnswer.botId</code> value; if that is also <code>0</code> or absent, no canned answers are fetched. Only evaluated when <code>useScript</code> is <code>true</code>. For the <code>input</code> object shape, see <strong>Post-processing function reference</strong> below.</td></tr><tr><td><code>cannedAnswer.defaultQueryText</code></td><td>String (JS function)</td><td>Not set</td><td>JavaScript function string that provides the canned answer search query when the primary query (derived from the ticket title and description) is empty. The function has the signature <code>function(input)</code>, where <code>input</code> is the ticket object. The function must return a string. A non-string return, an empty string, or an exception terminates the canned answer search with no results — there is no further fallback to the ticket title or any other value. Note: when this function is called, <code>input.queryText</code> contains the empty primary query; use other ticket fields such as <code>input.title</code> to construct a meaningful fallback. For the <code>input</code> object shape, see <strong>Post-processing function reference</strong> below.</td></tr><tr><td><code>cannedAnswer.favorites</code></td><td>Boolean</td><td><code>false</code></td><td>Enables the favorites canned answers endpoint, which returns answers the agent has marked as favorites.</td></tr></tbody></table>

**Examples:**

The following example shows a `cannedAnswer.script` function that routes to different bots based on ticket content type:

```javascript
function(input) {
  if (input.contentType === 'Incident') {
    return 1001;
  }
  return 1002;
}
```

The following example shows a `cannedAnswer.defaultQueryText` function. Note that `input.queryText` is empty when this function is called — use other ticket fields to construct the query:

```javascript
function(input) {
  return input.title;
}
```

#### Auto-complete

<table><thead><tr><th width="305">Property</th><th width="93">Type</th><th width="104">Default</th><th>Description</th></tr></thead><tbody><tr><td><code>autoCompleteTickets.datasourceId</code></td><td>String</td><td>Not set</td><td>The datasource(s) to search when generating auto-complete suggestions for ticket fields.</td></tr><tr><td><code>autoCompleteTickets.maxTickets</code></td><td>Integer</td><td><code>10</code></td><td>Maximum number of auto-complete suggestions returned.</td></tr></tbody></table>

#### Post-processing

<table><thead><tr><th width="237">Property</th><th width="108">Type</th><th width="98">Default</th><th width="660">Description</th></tr></thead><tbody><tr><td><code>postProcessing</code></td><td>String (JS function)</td><td>Not set</td><td>JavaScript function string that runs after all predictions are assembled, before results are returned to the caller. The function has the signature <code>function(input)</code>, where <code>input.ticketObj</code> is the originating ticket and <code>input.responseData</code> is the complete prediction output. Returning a non-empty value replaces <code>responseData</code> entirely; returning <code>null</code>, <code>undefined</code>, <code>[]</code>, or <code>{}</code>, or throwing an exception, leaves the original <code>responseData</code> unchanged. See <strong>Post-processing function reference</strong> below for input and output shapes.</td></tr><tr><td><code>predictionPostProcessing</code></td><td>Object</td><td><code>{}</code></td><td>Per-prediction-type JavaScript function strings that run before each type's results are returned. Valid keys: <code>predictions</code>, <code>relatedKBs</code>, <code>publicKBs</code>, <code>kbClassifier</code>, <code>similarTickets</code>, <code>sentiment</code>. Each value is a function string with the same signature as <code>postProcessing</code>. The same return behavior applies: non-empty replaces, empty or exception preserves. See <strong>Post-processing function reference</strong> below for shapes.</td></tr></tbody></table>

#### Post-processing function reference

Both `postProcessing` and `predictionPostProcessing` functions receive `function(input)`, where `input` has two properties: `ticketObj` and `responseData`.

**`ticketObj` fields:**

<table data-search="false"><thead><tr><th width="150">Field</th><th width="105">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>id</code></td><td>String</td><td>External ticket ID from the originating ITSM system.</td></tr><tr><td><code>ticketId</code></td><td>Number</td><td>Internal numeric ticket ID.</td></tr><tr><td><code>displayId</code></td><td>String</td><td>Human-readable ticket display identifier.</td></tr><tr><td><code>title</code></td><td>String</td><td>Ticket title or subject line.</td></tr><tr><td><code>description</code></td><td>String</td><td>Ticket body or description.</td></tr><tr><td><code>contentType</code></td><td>String</td><td>Ticket type, such as <code>"Incident"</code>.</td></tr><tr><td><code>dataSourceId</code></td><td>String</td><td>ID of the data source this ticket belongs to.</td></tr><tr><td><code>tenantId</code></td><td>String</td><td>Tenant ID.</td></tr><tr><td><code>isConversation</code></td><td>Boolean</td><td>Whether the ticket represents a multi-turn conversation.</td></tr><tr><td><code>agentId</code></td><td>String</td><td>ID of the handling agent. Present only when the request includes an agent ID header.</td></tr></tbody></table>

**`postProcessing`**  **`responseData` fields:**

The `responseData` object contains the assembled output from all active prediction models. Only keys for enabled prediction types are present; exact fields depend on the `predictions` array in **Prediction types**.

<table data-search="false"><thead><tr><th width="214">Field</th><th>Description</th></tr></thead><tbody><tr><td><code>predictions</code></td><td>Array of field prediction objects.</td></tr><tr><td><code>relatedKBs</code></td><td>Array of KB result objects.</td></tr><tr><td><code>similarTickets</code></td><td>Array of similar historical ticket objects.</td></tr><tr><td><code>sentiment</code></td><td>Sentiment result objects.</td></tr><tr><td><code>escalation</code></td><td>Escalation result objects.</td></tr><tr><td><code>cannedAnswers</code></td><td>Array of canned answer objects.</td></tr><tr><td><code>favoriteCannedAnswers</code></td><td>Array of favorited canned answer objects.</td></tr><tr><td><code>majorIncidents</code></td><td>Major incident objects.</td></tr><tr><td><code>ticketIntent</code></td><td>Ticket intent objects.</td></tr><tr><td><code>intents</code></td><td>Intent objects.</td></tr></tbody></table>

**`predictionPostProcessing`**  **`responseData` shape per key:**

<table data-header-hidden data-search="false"><thead><tr><th width="158"></th><th></th></tr></thead><tbody><tr><td>Key</td><td><code>responseData</code> shape</td></tr><tr><td><code>predictions</code></td><td>Array of field prediction objects.</td></tr><tr><td><code>relatedKBs</code></td><td>Array of KB result objects.</td></tr><tr><td><code>publicKBs</code></td><td>Array of KB result objects, same shape as <code>relatedKBs</code>.</td></tr><tr><td><code>kbClassifier</code></td><td>Array of KB result objects, same shape as <code>relatedKBs</code>.</td></tr><tr><td><code>sentiment</code></td><td>Sentiment result object.</td></tr><tr><td><code>similarTickets</code></td><td>Object <code>{ similarTickets: [...] }</code>, a wrapper object, not a plain array. Destructure accordingly.</td></tr></tbody></table>

**KB result object:**

<table data-search="false"><thead><tr><th width="159">Field</th><th width="113">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>title</code></td><td>String</td><td>Title of the KB article section.</td></tr><tr><td><code>documentKey</code></td><td>String</td><td>Unique document key.</td></tr><tr><td><code>documentTitle</code></td><td>String</td><td>Document-level title.</td></tr><tr><td><code>sectionTitle</code></td><td>String</td><td>Title of the matched section within the document.</td></tr><tr><td><code>url</code></td><td>String</td><td>URL to the KB article.</td></tr><tr><td><code>contentId</code></td><td>String</td><td>Content identifier.</td></tr><tr><td><code>source</code></td><td>String</td><td>Model that produced this result. One of <code>"bol"</code>, <code>"intent"</code>, <code>"keywordsearch"</code>, <code>"kb_classifier"</code>.</td></tr><tr><td><code>answerContents</code></td><td>Any</td><td>Structured answer content, if available.</td></tr><tr><td><code>content</code></td><td>String</td><td>Article text.</td></tr><tr><td><code>score</code></td><td>String</td><td>Confidence score as a decimal string. For example, <code>"87.50"</code>. This is a string, not a number.</td></tr><tr><td><code>intentId</code></td><td>String or null</td><td>Associated intent ID, if applicable.</td></tr><tr><td><code>datasourceId</code></td><td>String</td><td>ID of the data source containing this article.</td></tr><tr><td><code>isPrivate</code></td><td>Boolean</td><td>Whether the article is in a private knowledge base.</td></tr><tr><td><code>useful</code></td><td>Any</td><td>Agent feedback value, if populated.</td></tr><tr><td><code>feedbackValue</code></td><td>Any</td><td>Feedback value, if populated.</td></tr></tbody></table>

**Examples:**

The following example shows a `postProcessing` function that removes KB results below a custom score threshold:

```javascript
function(input) {
  if (input.responseData.relatedKBs) {
    input.responseData.relatedKBs = input.responseData.relatedKBs.filter(function(kb) {
      return parseFloat(kb.score) >= 85;
    });
  }
  return input.responseData;
}
```

The following examples show `predictionPostProcessing` functions for two keys. Note that `similarTickets` passes a wrapper object as `responseData` rather than a plain array — the function must return the same wrapper shape.

{% code overflow="wrap" %}

```javascript
// relatedKBs — responseData is a plain array
"function(input) { return input.responseData.filter(function(kb) { return kb.source === 'intent'; }); }"

// similarTickets — responseData is { similarTickets: [...] }, not a plain array
"function(input) { return { similarTickets: input.responseData.similarTickets.slice(0, 3) }; }"
```

{% endcode %}

#### Access control

<table><thead><tr><th width="134">Property</th><th width="107">Type</th><th width="99">Default</th><th width="712">Description</th></tr></thead><tbody><tr><td><code>aclKeys</code></td><td>Array of objects</td><td>Not set</td><td>Scopes KB search and Retrieval-Augmented Generation (RAG) results to the user's Access Control List (ACL) context. Each entry requires two properties: <code>name</code> and <code>valueMapping</code>. <code>name</code> must match an existing top-level key on the ticket object after <code>customFields</code> flattening, it acts as both the filter gate (entries are skipped when <code>name</code> does not exist on the ticket object) and the output key name in the session attributes. <code>valueMapping</code> is a dot-notation path that extracts the actual value from the ticket object. Ticket <code>customFields</code> are automatically flattened to the top level before path resolution: a custom field <code>{ key: "severity", value: "2" }</code> becomes <code>ticket.severity = "2"</code>. See <strong>ACL keys reference</strong> below for the available ticket fields.</td></tr></tbody></table>

#### ACL keys reference

`aclKeys` entries are evaluated against the following ticket object fields. Values in `customFields` are additionally flattened to the top level before path resolution.

| Field          | Type   | Notes                                                                                                                                      |
| -------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `id`           | String | External ticket ID.                                                                                                                        |
| `displayId`    | String | Human-readable display identifier.                                                                                                         |
| `title`        | String | Ticket title or subject line.                                                                                                              |
| `description`  | String | Ticket body or description.                                                                                                                |
| `status`       | Object | Ticket status object. Access nested fields with dot notation, for example `status.statusCode`.                                             |
| `category`     | String | Ticket category.                                                                                                                           |
| `type`         | String | Ticket type, for example `"Incident"`.                                                                                                     |
| `ticketUri`    | String | URL of the ticket in the originating ITSM system.                                                                                          |
| `agentId`      | String | ID of the handling agent.                                                                                                                  |
| `userEmail`    | String | Email address of the ticket submitter.                                                                                                     |
| `locale`       | String | Request locale, for example `"en_US"`.                                                                                                     |
| `queryText`    | String | Computed query text derived from the ticket.                                                                                               |
| `customFields` | Array  | Array of `{ key, value }` pairs. Each pair is also flattened to the top level as `ticket[key] = value` before `valueMapping` is evaluated. |

`valueMapping` uses dot-notation to traverse nested fields. For example, to extract `statusCode` from the `status` object, set `valueMapping` to `"status.statusCode"` and `name` to `"status"` so the filter gate passes.

{% hint style="warning" %}
If two entries share the same `name`, the second entry silently overwrites the first in the session attributes output.
{% endhint %}

```json
{
  "enabled": false,
  "queryFields": ["title", "description"],
  "preProcessing": {
    "title": "function(title){return title;}",
    "description": "function(description){var pre = 'Message: Short Description of Issue:';var i=description.toLowerCase().indexOf(pre.toLowerCase());if(i>=0){return description.substring(i+pre.length(), description.length());}return description;}"
  },
  "processing": {
    "method": "sequential",
    "fields": ["title", "title+description"],
    "sequentialStopScore": 90
  },
  "bolScoreFunc": "function(numOfBOLKB) {return 98*(0.85-0.04*numOfBOLKB)}",
  "confidenceScoreThreshold": {
    "intent": 80,
    "bol": 60,
    "keywordsearch": 30
  },
  "scoreNormalization": {
    "intent": "function(score){return (score-80)*(100-95)/(100-80)+95;}",
    "bol": "function(score){return (score-60)*(95-90)/(100-60)+90;}",
    "keywordsearch": "function(score){return (score-30)*(90-85)/(100-30)+85;}"
  },
  "maxNumOfKB": 5
}
```

{% hint style="info" %}
If the tenant has a value in the **Policy (Deprecated)** field in the TicketIQ Policy section but not in this field, the widget server migrates it automatically on first use.
{% endhint %}

### Widget

<table data-header-hidden><thead><tr><th width="150"></th><th></th></tr></thead><tbody><tr><td><strong>Type</strong></td><td>Text field (JSON)</td></tr><tr><td><strong>Default</strong></td><td>Not configured</td></tr></tbody></table>

Stores the layout and behavior configuration for a TicketAI widget embedded in an external ITSM system. A UUID identifies each widget, allowing a single tenant to run multiple independent widget instances. When the widget loads, it fetches this configuration by tenant ID and widget UUID. Changes take effect immediately on the next widget load.<br>

{% hint style="info" %}
This field controls widget UI behavior only. TicketAI prediction behavior is controlled separately by the **Policy** field.
{% endhint %}

#### Top-level properties

<table data-search="false"><thead><tr><th width="181">Property</th><th width="87">Type</th><th width="104">Default</th><th>Description</th></tr></thead><tbody><tr><td><code>appToken</code></td><td>String</td><td>Required</td><td>Authentication token for this widget instance. Used to authenticate all widget requests.</td></tr><tr><td><code>refreshToken</code></td><td>String</td><td><code>""</code></td><td>OAuth (Open Authorization) refresh token for the widget session.</td></tr><tr><td><code>externalSystemType</code></td><td>String</td><td>Required</td><td>The ITSM system this widget is embedded in. Valid values: <code>"zendesk"</code>, <code>"serviceNow"</code>, <code>"serviceNowWorkspace"</code>, <code>"jira"</code>, <code>"salesforce"</code>. Controls the default behavior for agent actions such as applying a KB article: Zendesk and ServiceNow default to a draft comment; Jira and Salesforce default to a persisted comment.</td></tr><tr><td><code>zendesk</code></td><td>Object</td><td><code>{}</code></td><td>System-specific configuration for Zendesk. Reserved for future use.</td></tr><tr><td><code>serviceNow</code></td><td>Object</td><td><code>{}</code></td><td>System-specific configuration for ServiceNow. Reserved for future use.</td></tr><tr><td><code>jira</code></td><td>Object</td><td><code>{}</code></td><td>System-specific configuration for Jira. Reserved for future use.</td></tr><tr><td><code>salesForce</code></td><td>Object</td><td><code>{}</code></td><td>System-specific configuration for Salesforce. Reserved for future use.</td></tr></tbody></table>

#### Widget layout

The `sections` array defines the widget's structure and contains three fixed sections: `top`, `main`, and `bottom`. All panels live within `main`, which uses a `"tabs"` layout, the only supported value. The two supported tabs are `resolution` and `recommendations`.

Each tab contains a `sections` array of panel objects. Every panel has a `name` identifying the panel type, an `enabled` flag, and a `config` object with panel-specific settings.

#### Resolution tab panels

The `resolution` tab surfaces AI-recommended solutions for the current ticket.

<table><thead><tr><th width="182">Panel</th><th>Description</th></tr></thead><tbody><tr><td><code>churn_analysis</code></td><td>Churn or risk analysis for the ticket. Accepts <code>config.labels</code>, an array of severity label strings, and <code>config.colors</code>, an array of color values in matching order.</td></tr><tr><td><code>ticket_summary</code></td><td>AI-generated summary of the ticket. Accepts the same <code>config.labels</code> and <code>config.colors</code> structure as <code>churn_analysis</code>.</td></tr><tr><td><code>predictions</code></td><td>Predicted values for ticket fields such as category or priority. Accepts <code>config.colors</code>, an object mapping severity keys <code>success</code>, <code>warning</code>, <code>error</code> to color values, and <code>config.excludeFields</code>, an array of field mapping IDs to hide.</td></tr><tr><td><code>kb</code></td><td>AI-recommended KB articles. See <strong>KB panel configuration</strong> below for the full set of options.</td></tr><tr><td><code>macro</code></td><td>AI-recommended macro responses. Accepts <code>config.amount</code>, <code>config.skip</code>, and <code>config.feedback</code>.</td></tr><tr><td><code>intent</code></td><td>The classified intent for the ticket. See <strong>Intent panel configuration</strong> below.</td></tr></tbody></table>

#### Recommendations tab panels

The `recommendations` tab surfaces related items for agent reference.

<table><thead><tr><th width="181">Panel</th><th>Description</th></tr></thead><tbody><tr><td><code>macro</code></td><td>Same configuration as the <code>macro</code> panel in the resolution tab.</td></tr><tr><td><code>kb</code></td><td>Same configuration as the <code>kb</code> panel in the resolution tab.</td></tr><tr><td><code>similar_tickets</code></td><td>Similar historical tickets. Accepts <code>config.amount</code> and <code>config.skip</code>.</td></tr><tr><td><code>major_incidents</code></td><td>Detected major incidents related to the ticket. Accepts <code>config.amount</code> and <code>config.skip</code>.</td></tr></tbody></table>

#### Common panel configuration

Most panels share these `config` properties:

<table><thead><tr><th width="229">Property</th><th width="94">Type</th><th width="111">Default</th><th>Description</th></tr></thead><tbody><tr><td><code>enabled</code></td><td>Boolean</td><td><code>true</code></td><td>Whether the panel is visible in the widget. Set to <code>false</code> to hide it.</td></tr><tr><td><code>config.amount</code></td><td>Integer</td><td>Varies</td><td>Maximum number of results to display in the panel.</td></tr><tr><td><code>config.skip</code></td><td>Integer</td><td><code>0</code></td><td>Number of results to skip. Used for pagination.</td></tr><tr><td><code>config.feedback.enabled</code></td><td>Boolean</td><td><code>true</code></td><td>Whether agents can submit thumbs-up/down feedback on results in this panel.</td></tr></tbody></table>

#### KB panel configuration

The `kb` panel accepts additional configuration:

<table><thead><tr><th width="154">Property</th><th width="108">Type</th><th width="125">Default</th><th>Description</th></tr></thead><tbody><tr><td><code>config.escalationFlow.enabled</code></td><td>Boolean</td><td><code>false</code></td><td>Whether to show a <strong>Refine Results</strong> escalation button in the KB panel.</td></tr><tr><td><code>config.escalationFlow.flowId</code></td><td>String</td><td><code>""</code></td><td>The flow ID to invoke when the escalation button is clicked. Requires <code>escalationFlow.enabled: true</code>.</td></tr><tr><td><code>config.slots.enabled</code></td><td>Boolean</td><td><code>true</code></td><td>Whether slot-filling is enabled when an agent applies a KB article.</td></tr><tr><td><code>config.onApply</code></td><td>Array of objects</td><td>See example</td><td>Defines the action taken when an agent clicks <strong>Apply</strong> on a KB article. Each entry specifies a <code>condition</code> and an <code>action</code>. Multiple entries allow different actions based on whether the comment is public or private.</td></tr></tbody></table>

`config.onApply` entry structure:

<table><thead><tr><th width="197">Property</th><th width="102">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>condition.isPrivate</code></td><td>Boolean</td><td>When <code>true</code>, this rule applies to private/internal comments. When <code>false</code>, it applies to public comments.</td></tr><tr><td><code>action</code></td><td>String</td><td>The action to take. Valid values: <code>"persistComment"</code> saves the comment immediately, <code>"draftComment"</code> stages the comment for agent review before saving, <code>"draftEmail"</code> opens an email compose window. Salesforce only.</td></tr></tbody></table>

#### Intent panel configuration

<table><thead><tr><th width="229">Property</th><th width="91">Type</th><th width="97">Default</th><th>Description</th></tr></thead><tbody><tr><td><code>config.amount</code></td><td>Integer</td><td><code>1</code></td><td>Number of primary intents to display.</td></tr><tr><td><code>config.feedback.enabled</code></td><td>Boolean</td><td><code>true</code></td><td>Whether agents can submit feedback on intent classifications.</td></tr><tr><td><code>config.alternateIntent.enabled</code></td><td>Boolean</td><td><code>true</code></td><td>Whether to show a dropdown of alternate intent suggestions alongside the primary classification.</td></tr><tr><td><code>config.alternateIntent.maxIntentsInList</code></td><td>Integer</td><td><code>20</code></td><td>Maximum number of alternate intents shown in the dropdown.</td></tr></tbody></table>

```json
{
  "appToken": "your-app-token-here",
  "refreshToken": "",
  "externalSystemType": "serviceNow",
  "zendesk": {},
  "serviceNow": {},
  "jira": {},
  "salesForce": {},
  "sections": [
    {
      "name": "top",
      "sections": []
    },
    {
      "name": "main",
      "layout": "tabs",
      "sections": [
        {
          "name": "resolution",
          "enabled": true,
          "sections": [
            {
              "name": "churn_analysis",
              "enabled": true,
              "config": {
                "labels": ["success", "Warning", "Error"],
                "colors": ["green", "yellow", "red"]
              }
            },
            {
              "name": "ticket_summary",
              "enabled": true,
              "config": {
                "labels": ["success", "Warning", "Error"],
                "colors": ["green", "yellow", "red"]
              }
            },
            {
              "name": "predictions",
              "enabled": true,
              "config": {
                "colors": {
                  "success": "green",
                  "warning": "yellow",
                  "error": "error"
                },
                "excludeFields": []
              }
            },
            {
              "name": "kb",
              "enabled": true,
              "config": {
                "amount": 2,
                "skip": 0,
                "feedback": { "enabled": true },
                "escalationFlow": {
                  "enabled": false,
                  "flowId": ""
                },
                "slots": { "enabled": true },
                "onApply": [
                  { "condition": { "isPrivate": true }, "action": "persistComment" },
                  { "condition": { "isPrivate": false }, "action": "persistComment" }
                ]
              }
            },
            {
              "name": "macro",
              "enabled": true,
              "config": {
                "amount": 2,
                "skip": 0,
                "feedback": { "enabled": true }
              }
            },
            {
              "name": "intent",
              "enabled": true,
              "config": {
                "amount": 1,
                "feedback": { "enabled": true },
                "alternateIntent": {
                  "enabled": true,
                  "maxIntentsInList": 20
                }
              }
            }
          ]
        },
        {
          "name": "recommendations",
          "enabled": true,
          "sections": [
            {
              "name": "macro",
              "enabled": false,
              "config": {
                "amount": 2,
                "skip": 0,
                "feedback": { "enabled": true }
              }
            },
            {
              "name": "kb",
              "enabled": false,
              "config": {
                "amount": 2,
                "skip": 0,
                "feedback": { "enabled": true },
                "escalationFlow": { "enabled": false, "flowId": "" },
                "slots": { "enabled": true },
                "onApply": [
                  { "condition": { "isPrivate": true }, "action": "persistComment" },
                  { "condition": { "isPrivate": false }, "action": "persistComment" }
                ]
              }
            },
            {
              "name": "similar_tickets",
              "enabled": true,
              "config": {
                "amount": 3,
                "skip": 0
              }
            },
            {
              "name": "major_incidents",
              "enabled": true,
              "config": {
                "amount": 2,
                "skip": 0
              }
            }
          ]
        }
      ]
    },
    {
      "name": "bottom",
      "sections": []
    }
  ]
}
```

{% hint style="info" %}
The widget UUID must be a valid UUID v4. The system rejects non-conforming IDs.
{% endhint %}

### Consolidated API Timeout (in seconds)

| **Type**    | Integer |
| ----------- | ------- |
| **Default** | `60`    |

Sets the maximum number of seconds the consolidated predictions API waits for TicketAI results before returning a response. When the API is called, it dispatches prediction jobs to a background thread and waits up to this value for them to complete: field predictions, related KBs, similar tickets, and major incidents. If predictions finish in time, the API returns full results. If the timeout expires, the API returns immediately with empty arrays for all prediction types rather than hanging the caller. Increase this value if your tenant's prediction workload regularly exceeds 60 seconds; decrease it if you need the integration to fail fast.

{% hint style="info" %}
On timeout, the API returns empty result arrays rather than an error.
{% endhint %}

{% hint style="info" %}
This timeout applies only to the tenant-server consolidated predictions path. The widget server uses a separate hardcoded timeout and is not affected by this field.
{% endhint %}

### Use Tenant server for Testing API. Default is ticketiq widgets server

| **Type**    | Checkbox |
| ----------- | -------- |
| **Default** | Disabled |

Routes TicketAI consolidated prediction requests to `tenant-server` instead of the widget server's built-in prediction engine. By default, the widget server handles both `/v1/service/consolidated-predictions` and `/v1/service/auto-resolution-prediction` using its own internal logic. When enabled, both endpoints proxy requests directly to the `tenant-server` and return that response. Enable this when you need to test or validate the `tenant-server` prediction path against live traffic without changing client-side integrations.

{% hint style="warning" %}
This is a routing switch intended for testing and diagnosis, not permanent production use.
{% endhint %}

{% hint style="info" %}
This setting affects only the two consolidated prediction endpoints. All other widget server endpoints continue to use their default logic.
{% endhint %}

See also: [Consolidated API Timeout (in seconds)](#consolidated-api-timeout-in-seconds)

### Use Title First in TicketAI RelatedKbs instead of description

| **Type**    | Checkbox |
| ----------- | -------- |
| **Default** | Disabled |

When enabled, related KB searches use the ticket title as the primary query and description as secondary. When disabled, the engine uses description as the primary query. This setting only applies when `enabled` is `false` in the **Policy** JSON; if Policy is active, its `queryFields` and `preProcessing` settings govern query field ordering instead.

See also: [Policy](#policy)

### Enable escalation report notification through email

| **Type**    | Checkbox |
| ----------- | -------- |
| **Default** | Disabled |

Controls whether the Aisera Platform sends escalation report emails to configured recipients. When disabled, all escalation notification jobs exit immediately without sending any emails, regardless of how notifications are configured.

When enabled, escalation notifications run in two modes. In real-time mode, the Aisera Platform sends a notification per ticket immediately when escalation occurs. In scheduled mode, a batch email covers all matched tickets on a configured interval (daily, weekly, or monthly). Both modes use HTML email templates with recipients, trigger conditions, and templates defined per notification record.

Enable this when your team needs proactive email alerts about ticket escalations, either as they occur or in periodic digest form. This is most useful for support managers or on-call teams who monitor escalation trends but are not watching the TicketIQ dashboard in real time.

{% hint style="warning" %}
&#x20;Enabling this flag alone does not configure escalation emails. Create notification records defining recipients, schedules, and templates via the escalation notifications API before enabling this setting. Email delivery also requires a working `MailClient` integration configured for the tenant.
{% endhint %}

{% hint style="warning" %}
Disabling this flag silently suppresses all escalation emails; jobs complete with a `SUCCEEDED` status but the Aisera Platform sends no emails. The manual trigger endpoint (`POST /v1/tenants/{tenantId}/escalationnotifications/trigger`) also respects this setting.
{% endhint %}
