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

# AI

> AI-powered analysis, chat assistant, natural language rule parsing, intelligence tools, recommendation management, and document import. Supports LLM and ML Worker backends.

## POST /api/v1/ai/chat

Stream a conversation with the AI assistant. Responses are streamed token-by-token with multi-step tool calling support (up to 5 tool steps per message). Messages are persisted to the database after the stream completes. Conversations are auto-created if no `conversationId` is provided.

Rate limited to **30 requests per 60 seconds** per user. Request timeout: 60 seconds.

### Headers

| Header         | Required | Description                                |
| -------------- | -------- | ------------------------------------------ |
| `Content-Type` | Yes      | `application/json`                         |
| `X-Tenant-Id`  | Yes      | Tenant identifier                          |
| `X-User-Id`    | No       | User identifier for conversation ownership |

### Request Body

| Field            | Type   | Required | Description                                                                                                                                                                       |
| ---------------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `messages`       | array  | Yes      | AI SDK message array. Each message has `role` (`user`, `assistant`, `tool`) and `content` (string or parts array). Must not be empty.                                             |
| `route`          | string | No       | Current page route for context routing (e.g., `"/studio/decision-flows"`). Determines which tools and system prompt are used. Defaults to `"/"` (general context with all tools). |
| `conversationId` | string | No       | Existing conversation ID to continue. If omitted, a new conversation is auto-created. When provided, persisted message history is prepended to the messages array.                |

### Response

Returns a streaming response in the AI SDK UI message format (Server-Sent Events). The response includes:

* `X-Conversation-Id` header with the conversation ID for follow-up messages
* Streamed text chunks and tool call results in AI SDK wire format

### Roles

admin, editor, viewer

### Example

```bash theme={null}
curl -N -X POST https://playground.kaireonai.com/api/v1/ai/chat \
  -H "Content-Type: application/json" \
  -H "X-Tenant-Id: my-tenant" \
  -d '{
    "messages": [{ "role": "user", "content": "What are my top performing offers?" }],
    "route": "/dashboards"
  }'
```

### Context Routing

The `route` parameter determines which tools are available:

| Route Pattern            | Module     | Tools Available                                                                                                                                                                                                         |
| ------------------------ | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `/data/schemas`          | Data       | 18 read-only + addSchemaField, getSchemaFields, createSchema                                                                                                                                                            |
| `/data/flow-pipelines`   | Data       | 18 read-only + getSchemaFields, createFlowPipeline, updateFlowPipeline                                                                                                                                                  |
| `/data/*`                | Data       | 18 read-only + createConnector, testConnector                                                                                                                                                                           |
| `/studio/decision-flows` | Studio     | 18 read-only + 30+ creation, mutation, V2 pipeline, and intelligence tools                                                                                                                                              |
| `/studio/treatments`     | Studio     | 18 read-only + creation tools + content generation + 8 CMS tools                                                                                                                                                        |
| `/algorithms/*`          | Algorithms | 18 read-only + model management (trainModel, createExperiment, getModelDetails, addPredictor, removePredictor, updateModelConfig) + analyzeModelHealth, explainModelScoring, suggestModelImprovements, detectModelDrift |
| `/dashboards/*`          | Dashboards | 18 read-only + runHealthCheck, analyzeOfferPerformance, analyzePolicyConflicts                                                                                                                                          |
| Any other                | General    | All 105+ tools                                                                                                                                                                                                          |

***

## GET /api/v1/ai/conversations

List recent conversations for the tenant (up to 50, newest first). Requires session authentication.

### Response

```json theme={null}
[
  {
    "id": "conv_abc123",
    "title": "What are my top performing offers?",
    "userId": "user_001",
    "createdAt": "2026-03-18T10:30:00Z",
    "updatedAt": "2026-03-18T10:35:00Z",
    "user": { "name": "Admin User", "image": null }
  }
]
```

### Roles

Requires session authentication (any authenticated user).

***

## POST /api/v1/ai/conversations

Create a new conversation.

### Request Body

| Field   | Type   | Required | Description                                       |
| ------- | ------ | -------- | ------------------------------------------------- |
| `title` | string | No       | Conversation title. Default: `"New conversation"` |

**Response:** `201 Created` with the created conversation object.

### Roles

Requires session authentication.

***

## GET /api/v1/ai/conversations/{id}

Get a single conversation with its full message history, ordered by creation time ascending.

### Path Parameters

| Parameter | Type   | Description     |
| --------- | ------ | --------------- |
| `id`      | string | Conversation ID |

### Response

```json theme={null}
{
  "id": "conv_abc123",
  "title": "What are my top performing offers?",
  "tenantId": "my-tenant",
  "userId": "user_001",
  "createdAt": "2026-03-18T10:30:00Z",
  "updatedAt": "2026-03-18T10:35:00Z",
  "messages": [
    { "role": "user", "content": "What are my top performing offers?", "createdAt": "2026-03-18T10:30:00Z" },
    { "role": "assistant", "content": "Based on the data...", "toolInvocations": [...], "createdAt": "2026-03-18T10:30:05Z" }
  ]
}
```

### Roles

Requires session authentication. Only conversations belonging to the user's tenant are accessible.

***

## DELETE /api/v1/ai/conversations/{id}

Delete a conversation and all its messages. Only deletes conversations belonging to the authenticated user's tenant.

### Path Parameters

| Parameter | Type   | Description     |
| --------- | ------ | --------------- |
| `id`      | string | Conversation ID |

### Response

```json theme={null}
{ "ok": true }
```

### Roles

admin, editor

***

## POST /api/v1/ai/analyze/{type}

Run AI-powered analysis on tenant data. Supports three analysis types with automatic LLM/ML Worker routing based on data volume. The ML row threshold is 5,000 rows.

### Path Parameters

| Parameter | Values                                  | Description                 |
| --------- | --------------------------------------- | --------------------------- |
| `type`    | `"policies"`, `"segments"`, `"content"` | Type of analysis to perform |

### Request Body

| Field           | Type                     | Required                | Description                                                |
| --------------- | ------------------------ | ----------------------- | ---------------------------------------------------------- |
| `schemaId`      | string                   | Required for `segments` | ID of the data schema to analyze                           |
| `confirmed`     | boolean                  | No                      | Set `true` to bypass the large-dataset confirmation dialog |
| `preferredTier` | `"llm"` or `"ml_worker"` | No                      | Override automatic tier routing                            |

### Analysis Types

| Type       | Data Source                            | LLM Method                                                                                     | ML Worker Method                                           |
| ---------- | -------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
| `segments` | Data schema rows and field statistics  | LLM proposes structured segment definitions from field summaries                               | K-Means clustering with silhouette scoring on full dataset |
| `policies` | Interaction history                    | LLM proposes structured policy thresholds from channel breakdown and frequency bands           | Statistical frequency binning and correlation analysis     |
| `content`  | Treatment / creative records (max 200) | LLM proposes structured content recommendations from creative metadata and performance metrics | TF-IDF and statistical content pattern analysis            |

### Response -- Confirmation Required

When the dataset exceeds 5,000 rows and `confirmed` is not `true`:

```json theme={null}
{
  "requiresConfirmation": true,
  "tier": "ml_worker",
  "warning": "Dataset has 50,000 rows. For datasets over 5,000 rows, enable the ML Worker for faster and cheaper analysis.",
  "confirmationMeta": {
    "rowCount": 50000,
    "accuracy": "ML Worker uses K-Means clustering, logistic regression, and TF-IDF -- more accurate than LLM pattern matching for large datasets.",
    "costEstimate": "Proceeding with LLM will use approximately 3,750,500 tokens (~$0.56).",
    "speedComparison": "ML Worker processes locally in seconds vs LLM round-trip latency."
  }
}
```

### Response -- Analysis Complete

Actionable findings are also persisted as [AI recommendations](#get-apiv1airecommendations) (dedupe
skips ones already in the inbox), so the response echoes what was written:

```json theme={null}
{
  "results": [
    { "name": "High-Value Loyalists", "description": "...", "size": 2340, "percentage": 18, "filterRules": [...], "characteristics": [...], "suggestedUse": "..." }
  ],
  "source": "llm",
  "analysisType": "segments",
  "recommendations": [
    { "id": "rec_...", "type": "segment", "title": "...", "status": "new", "source": "llm" }
  ],
  "recommendationsCreated": 3,
  "recommendationsSkipped": 1
}
```

| Field                    | Type   | Description                                                   |
| ------------------------ | ------ | ------------------------------------------------------------- |
| `results`                | array  | The raw analyzer output (segment/policy/content proposals).   |
| `source`                 | string | `"llm"` for this path.                                        |
| `analysisType`           | string | Echoes the path `type`.                                       |
| `recommendations`        | array  | The `AiRecommendation` rows created from actionable findings. |
| `recommendationsCreated` | number | Count of new recommendation rows written.                     |
| `recommendationsSkipped` | number | Count skipped as duplicates of existing inbox items.          |

### Response -- ML Worker Job Submitted

```json theme={null}
{
  "jobId": "job_abc123",
  "source": "ml_worker",
  "analysisType": "segments"
}
```

### Heuristic Fallbacks

Each analyzer falls back to deterministic heuristics if the LLM call fails:

* **Segments**: Splits the most variable numeric field into Low/Mid/High at the 33rd and 67th percentiles
* **Policies**: Finds the frequency band with the highest conversion rate and recommends a cap one step below the drop-off point
* **Content**: Computes CTR/CVR per creative, flags underperformers below 50% of average, highlights top performers above 150% of average

### Roles

admin, editor

***

## GET /api/v1/ai/analyzer-settings

Get current AI analyzer settings for the tenant with defaults filled in. Returns five sections: segmentation, policy, content, ruleBuilder, and ranking.

### Response

```json theme={null}
{
  "segmentation": {
    "minClusters": 2,
    "maxClusters": 8,
    "algorithm": "kmeans",
    "includedFeatures": null
  },
  "policy": {
    "dailyCap": 3,
    "weeklyCap": 10,
    "monthlyCap": 30,
    "lookbackDays": 90,
    "minSampleSize": 100
  },
  "content": {
    "minImpressions": 100,
    "metricWeights": { "ctr": 0.33, "cvr": 0.34, "revenue": 0.33 },
    "confidenceLevel": 0.95
  },
  "ruleBuilder": {
    "maxConditions": 5,
    "allowedOperators": ["equals", "gt", "lt", "gte", "lte", "contains", "in"],
    "fieldTypeConstraints": null
  },
  "ranking": {
    "lagrangianEnabled": false,
    "crossOfferEnabled": false,
    "exp3IxEnabled": false,
    "budgetPacingEnabled": false,
    "goalSeekEnabled": false
  }
}
```

### Roles

admin, editor, viewer

***

## PUT /api/v1/ai/analyzer-settings

Update AI analyzer settings. Each sub-schema is validated independently using Zod. Invalid values in one section do not affect valid values in other sections. Settings are persisted to the `TenantSettings` table and take effect on the next `/recommend` call (no server restart required).

### Request Body

Partial object matching the response structure of `GET /api/v1/ai/analyzer-settings`. Only include sections you want to update.

```json theme={null}
{
  "segmentation": { "maxClusters": 12 },
  "policy": { "lookbackDays": 180 },
  "ranking": { "lagrangianEnabled": true }
}
```

#### `ranking` object

Controls realtime ranking features that run inside the `/recommend` hot path. All flags default to `false` (opt-in).

| Field                 | Type    | Default | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| --------------------- | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `lagrangianEnabled`   | boolean | `false` | Enable the realtime Lagrangian shadow-price solver. When `true`, the rank node applies a soft penalty to offers whose `budget.dailyCapCents` is binding — offers near their daily budget cap receive a downward score adjustment proportional to how constrained they are, naturally rotating traffic toward less-saturated offers. When no offer has budget or inventory configured, the solver detects a no-constraint state and returns base scores untouched. See [Lagrangian constraint solver](#lagrangian-constraint-solver) below. |
| `crossOfferEnabled`   | boolean | `false` | Enable cross-offer constraint loading on the **batch** path (`batch-executor.ts`). Requires `lagrangianEnabled: true` to have effect. Note: on the realtime `/recommend` path, cross-offer constraints are loaded whenever `lagrangianEnabled` is `true` — `crossOfferEnabled` is a batch-path-only gate. See [Cross-Offer Constraints API](/api-reference/cross-offer-constraints#runtime-paths).                                                                                                                                         |
| `exp3IxEnabled`       | boolean | `false` | Enable the EXP3-IX online bandit. Requires operator-configured `banditArms` in the `ranking` state block. Without configured arms the flag is a structured no-op. Full arm configuration is documented at [EXP3-IX Ranking](/decisioning/ranking-exp3ix).                                                                                                                                                                                                                                                                                  |
| `budgetPacingEnabled` | boolean | `false` | Enable budget pacing so daily offer spend is spread evenly across the day. Requires per-offer pacing config (`plan` + `delivered` history). Without that config the wire reports `awaitingConfig: true` and leaves scores untouched. Operator configuration interface is forthcoming.                                                                                                                                                                                                                                                      |
| `goalSeekEnabled`     | boolean | `false` | Enable goal-seek adjustment that fine-tunes ranking weights toward a configured period target (e.g. a conversion-rate goal). Requires `periodTarget`, `currentActual`, and `elapsedFraction` in the ranking state. Without those values the wire is a no-op. Operator configuration interface is forthcoming.                                                                                                                                                                                                                              |

#### Lagrangian constraint solver

When `lagrangianEnabled` is `true`, the `/recommend` hot path calls `applyRealtimeRanking` (per `src/lib/ranking/realtime-wire.ts`) after the score node and before the final sort. The solver models two types of per-offer constraints:

* **`budget_daily:<offerId>`** — built from `offer.budget.dailyCapCents` and `currentDailySpentCents`. Remaining budget = `cap − spent` (resets each calendar day). An offer with no remaining budget yields no constraint and is not penalized.
* **`inventory:<offerId>`** — built from `offer.inventory.remainingStock`.

When no binding constraint exists across all candidates (no offer has `budget` or `inventory` configured, or all remaining values are zero), the solver detects this and returns `noOp: true` — base scores are returned untouched and the solver overhead is skipped. A solver failure (rare) returns `solverFailed: true` and falls back to base scores; the failure is logged at `error` level for operator alerting.

The solver does not drop offers below zero score — it applies a continuous shadow-price penalty that rotates traffic, not a hard filter. Hard filters (offers that have already exhausted their cap) run earlier in the pipeline via `src/lib/ranking/constraints.ts` regardless of this flag.

### Response

Returns the full merged settings object (same format as GET).

### Roles

admin only

***

## POST /api/v1/ai/intelligence

Dispatch to intelligence backend tools. This is a bridge endpoint used by the MCP server (which runs as a separate stdio process and cannot import Next.js server modules directly).

### Request Body

| Field    | Type   | Required | Description                                            |
| -------- | ------ | -------- | ------------------------------------------------------ |
| `tool`   | string | Yes      | Tool name (see table below)                            |
| `params` | object | Yes      | Tool-specific parameters. `tenantId` is auto-injected. |

### Available Tools

| Tool                              | Parameters                                                | Description                                                                           |
| --------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| `explainDecision`                 | `customerId`, `offerId?`, `decisionFlowKey?`              | Explain why a customer received or did not receive an offer. Full funnel walkthrough. |
| `compareOfferEligibility`         | `customerId`, `offerIds` (2-5)                            | Side-by-side offer eligibility comparison.                                            |
| `listCustomerSuppressions`        | `customerId`                                              | Active contact policy suppressions for a customer.                                    |
| `traceCustomerJourney`            | `customerId`, `limit?` (default 20)                       | Customer activity timeline: interactions, journeys, experiments.                      |
| `analyzeQualificationFunnel`      | `decisionFlowKey?`                                        | Funnel analysis identifying bottleneck rules.                                         |
| `analyzeContactPolicySuppression` | `channel?`, `period?` (`day`/`week`/`month`)              | Suppression rate analysis by rule type and channel.                                   |
| `analyzePolicyConflicts`          | (none)                                                    | Detect conflicts across offers, rules, policies, experiments.                         |
| `analyzeOfferPerformance`         | `period?` (`day`/`week`/`month`), `limit?` (default 20)   | Offer performance with trends and actionable insights.                                |
| `simulateRuleChange`              | `ruleId`, `proposedChange: { field, oldValue, newValue }` | Before/after reach estimation for rule changes.                                       |
| `simulateFrequencyCapChange`      | `channel`, `currentCap`, `newCap`, `period`               | Impact of frequency cap change: customers unlocked/suppressed, fatigue risk.          |
| `analyzeModelHealth`              | `modelId`                                                 | Model metrics, performance trend, feature importance, data freshness, health verdict. |
| `explainModelScoring`             | `modelId`, `customerId`                                   | Score breakdown: raw score, percentile, contributing features with direction.         |
| `suggestModelImprovements`        | `modelId`                                                 | Missing predictors, model type advice, unused features, hyperparameter suggestions.   |
| `detectModelDrift`                | `modelId`                                                 | Distribution shift detection, calibration check, action recommendation.               |
| `runHealthCheck`                  | (none)                                                    | Comprehensive tenant health check (cached 5 minutes).                                 |
| `analyzeCrossModule`              | (none)                                                    | Cross-module correlation analysis (see below).                                        |

#### `analyzeCrossModule` Response

Returns insights that connect dots across modules -- the platform's most valuable intelligence layer.

```json theme={null}
{
  "tenantId": "tenant_001",
  "analyzedAt": "2026-03-26T15:34:24.991Z",
  "insights": [
    {
      "id": "policy-blocks-offer_001",
      "type": "policy_blocks_top_offer",
      "severity": "warning",
      "title": "Category suppression may limit \"Win-Back Lapsed Policy\"",
      "description": "\"Win-Back Lapsed Policy\" has a 9.8% CVR (top performer) but category suppression may be blocking repeat impressions.",
      "impact": "Potential revenue loss: relaxing this policy for high-value customers could increase conversions by 10-20%.",
      "action": {
        "label": "Review Policy",
        "type": "navigate",
        "href": "/studio/contact-policies"
      },
      "entities": [
        { "type": "offer", "id": "offer_001", "name": "Win-Back Lapsed Policy" },
        { "type": "contactPolicy", "id": "cp_001", "name": "Category Suppression After Dismiss" }
      ]
    }
  ],
  "summary": { "total": 4, "critical": 0, "warning": 4 }
}
```

| Correlation Type               | Severity | What It Detects                                           |
| ------------------------------ | -------- | --------------------------------------------------------- |
| `policy_blocks_top_offer`      | warning  | Contact policy suppressing a high-CVR offer               |
| `low_auc_model_in_active_flow` | critical | Near-random model active while decision flow is published |
| `zero_cvr_high_spend`          | warning  | Offer with many impressions but zero conversions          |

Each insight includes an `action` object that the UI renders as a one-click button (either navigation or direct API call).

### Response

Returns the tool's result directly as JSON. Errors return `500` with `{ "error": { "message": "..." } }`.

### Roles

admin, editor, viewer

***

## POST /api/v1/ai/parse-rule

Parse a natural language rule description into structured entity definitions. Uses LLM-powered parsing with regex-based heuristic fallback if the LLM call fails.

The parser loads the tenant's data schemas to provide field context to the LLM. It produces three entity types: `qualification_rule`, `contact_policy`, and `behavioral_metric`.

### Request Body

| Field  | Type   | Required | Description                       |
| ------ | ------ | -------- | --------------------------------- |
| `text` | string | Yes      | Natural language rule description |

### Response

```json theme={null}
{
  "entities": [
    {
      "type": "qualification_rule",
      "name": "Age >= 25",
      "payload": {
        "conditions": [{ "field": "customer.age", "operator": "gte", "value": 25 }],
        "ruleType": "hard"
      }
    },
    {
      "type": "behavioral_metric",
      "name": "total_spend_90d",
      "payload": {
        "aggregation": "sum",
        "field": "revenue",
        "windowDays": 90,
        "description": "Sum of revenue from conversions over the last 90 days"
      }
    },
    {
      "type": "qualification_rule",
      "name": "Spend >= $500 (90d)",
      "payload": {
        "conditions": [{ "field": "total_spend_90d", "operator": "gte", "value": 500 }],
        "ruleType": "hard"
      }
    }
  ],
  "warnings": [
    "No 'age' field found in available schemas. The rule may not work until the field is added."
  ]
}
```

### Heuristic Fallback Patterns

When the LLM is unavailable, the parser handles these regex patterns:

| Pattern             | Example Input                       | Parsed Entity                                                 |
| ------------------- | ----------------------------------- | ------------------------------------------------------------- |
| Age comparison      | "over 25", "aged 18 or older"       | `qualification_rule` with `customer.age >= N`                 |
| Spend threshold     | "spent more than \$500"             | `qualification_rule` with `customer.total_spend >= N`         |
| Time-windowed spend | "spent \$500 in the last 90 days"   | `behavioral_metric` (sum revenue, 90d) + `qualification_rule` |
| Negation            | "haven't converted on credit card"  | `contact_policy` with outcome-based suppression               |
| Category detection  | "credit card offers", "loan offers" | Scope applied to contact policy                               |

### Example

```bash theme={null}
curl -X POST https://playground.kaireonai.com/api/v1/ai/parse-rule \
  -H "Content-Type: application/json" \
  -H "X-Tenant-Id: my-tenant" \
  -d '{ "text": "Only show to customers over 25 who spent more than $500 in the last 90 days and haven'\''t converted on credit card offers" }'
```

### Roles

admin, editor

***

## GET /api/v1/ai/recommendations

List AI-generated recommendations for the tenant, ordered by creation date descending.

### Query Parameters

| Parameter | Type   | Values                                                                   | Description                   |
| --------- | ------ | ------------------------------------------------------------------------ | ----------------------------- |
| `type`    | string | `policy`, `rule`, `segment`, `content`, `experiment`, `model`, `weights` | Filter by recommendation type |
| `status`  | string | `new`, `reviewed`, `applied`, `dismissed`                                | Filter by status              |

The `experiment`, `model`, and `weights` types are Autopilot proposals (champion promotion, model retrain/pause, ranking-weight change).

### Response

```json theme={null}
{
  "recommendations": [
    {
      "id": "rec_001",
      "tenantId": "my-tenant",
      "type": "policy",
      "title": "Set frequency cap to 3/week",
      "description": "Analysis of 12,500 interactions shows conversion rate peaks at 3 contacts/week (8.2%). Rate drops 65% at 7 contacts/week.",
      "payload": {
        "ruleType": "frequency_cap",
        "scope": "global",
        "maxContactsPerWeek": 3
      },
      "confidence": "high",
      "source": "llm",
      "status": "new",
      "metadata": {
        "frequencyBands": [
          { "contactsPerWeek": 1, "conversionRate": 0.05 },
          { "contactsPerWeek": 3, "conversionRate": 0.082 }
        ],
        "estimatedImpact": "Applying this cap would optimize contact frequency for better conversion."
      },
      "createdAt": "2026-03-18T10:00:00Z"
    }
  ]
}
```

### Roles

admin, editor, viewer

***

## POST /api/v1/ai/recommendations

Create a new AI recommendation manually.

### Request Body

| Field         | Type   | Required | Description                                                              |
| ------------- | ------ | -------- | ------------------------------------------------------------------------ |
| `type`        | string | Yes      | `policy`, `rule`, `segment`, `content`, `experiment`, `model`, `weights` |
| `title`       | string | Yes      | Short summary                                                            |
| `description` | string | Yes      | Detailed explanation                                                     |
| `payload`     | object | No       | Machine-readable entity definition                                       |
| `confidence`  | string | No       | `high`, `medium`, `low`. Default: `medium`                               |
| `source`      | string | No       | `llm`, `ml_worker`, `autopilot`, `sentinel`. Default: `llm`              |
| `metadata`    | object | No       | Additional context data                                                  |

### Response

`201 Created` with the created recommendation object.

### Roles

admin, editor

***

## GET /api/v1/ai/recommendations/{id}

Get a single recommendation by ID.

### Path Parameters

| Parameter | Type   | Description       |
| --------- | ------ | ----------------- |
| `id`      | string | Recommendation ID |

### Response

```json theme={null}
{
  "recommendation": { "id": "rec_001", "type": "policy", "title": "...", "status": "new", ... }
}
```

### Roles

admin, editor, viewer

***

## PATCH /api/v1/ai/recommendations/{id}

Update the status of a recommendation.

### Path Parameters

| Parameter | Type   | Description       |
| --------- | ------ | ----------------- |
| `id`      | string | Recommendation ID |

### Request Body

| Field    | Type   | Required | Values                             |
| -------- | ------ | -------- | ---------------------------------- |
| `status` | string | Yes      | `reviewed`, `applied`, `dismissed` |

### Response

```json theme={null}
{
  "recommendation": { "id": "rec_001", "status": "reviewed", ... }
}
```

### Roles

admin, editor

***

## POST /api/v1/ai/recommendations/{id}/apply

Apply an AI recommendation by creating the corresponding entity in **draft** status. The created entity type depends on the recommendation type.

### Path Parameters

| Parameter | Type   | Description       |
| --------- | ------ | ----------------- |
| `id`      | string | Recommendation ID |

### Entity Creation by Type

| Recommendation Type | Entity Created               | Module                     |
| ------------------- | ---------------------------- | -------------------------- |
| `policy`            | Contact Policy (draft)       | Studio > Contact Policies  |
| `rule`              | Decisioning Gate (draft)     | Studio > Decisioning Gates |
| `segment`           | Customer Segment (draft)     | AI > Segments              |
| `content`           | Treatment / Creative (draft) | Studio > Treatments        |

### Response

```json theme={null}
{
  "entityId": "cp_001",
  "entityType": "policy"
}
```

The recommendation status is updated to `applied` with the `appliedEntityId` set to the created entity's ID. Returns `400` if the recommendation has already been applied, or if the recommendation `type` is not one the applier handles (`Unknown recommendation type: ...`). Autopilot-sourced types (`experiment`, `model`, `weights`) are normally actioned through the [Autopilot cron](./cron#get-apiv1cronai-autopilot) / approval flow rather than this endpoint.

### Roles

admin only

***

## POST /api/v1/ai/mutations/confirm

Explicitly approve a pending AI mutation **preview**. When the chat assistant proposes a change, it
stages a preview instead of writing; the AI panel's **Approve** button calls this endpoint directly
(no LLM round-trip), so approval is deterministic and audit-logged against the actor's tenant.

### Request Body

| Field        | Type          | Required | Description                                   |
| ------------ | ------------- | -------- | --------------------------------------------- |
| `mutationId` | string (uuid) | Yes      | Id of the staged mutation preview to execute. |

### Response `200`

```json theme={null}
{ "ok": true, "result": { "...": "the executed mutation's result" } }
```

### Errors

| Code  | Reason                                                                              |
| ----- | ----------------------------------------------------------------------------------- |
| `400` | `mutationId` missing or not a UUID.                                                 |
| `404` | Mutation not found or expired — ask the assistant to prepare it again.              |
| `502` | The mutation was approved but execution failed (`{ "ok": false, "error": "..." }`). |

### Roles

admin, editor

***

## GET /api/v1/ai/ml-worker/status

Check ML Worker connectivity status. Checks the tenant's ML Worker configuration from settings, with an environment variable fallback (`ML_WORKER_URL`). The health check endpoint (`/health`) uses a 30-second cache TTL and 3-second timeout.

### Response -- Connected

```json theme={null}
{
  "connected": true,
  "url": "https://ml-worker.internal:8080"
}
```

### Response -- Not Connected

```json theme={null}
{
  "connected": false
}
```

### Roles

admin, editor, viewer

***

## GET /api/v1/ai/config-status

Returns whether the configured AI provider has the credentials it needs to run. The endpoint reads the merged tenant + environment AI configuration and reports `configured: true` when the provider does not need an API key (`ollama`, `lm_studio`) or when an API key is present.

### Response

```json theme={null}
{
  "configured": true,
  "provider": "openai"
}
```

<ResponseField name="configured" type="boolean">
  `true` when the provider does not need a key (`ollama` or `lm_studio`) or a key is configured. `false` only when a key-required provider has no key set.
</ResponseField>

<ResponseField name="provider" type="string">
  The active AI provider id resolved from the merged tenant + environment configuration. Examples: `"openai"`, `"anthropic"`, `"ollama"`, `"lm_studio"`.
</ResponseField>

### Roles

admin, editor, viewer

### Example

```bash theme={null}
curl https://playground.kaireonai.com/api/v1/ai/config-status \
  -H "X-API-Key: krn_your_api_key" \
  -H "X-Tenant-Id: 5a9904b9-..."
```

***

## POST /api/v1/ai/imports/{id}/apply

Atomic apply of operator verdicts on AI-extracted import proposals. Every verdict in the request is wrapped in a single database transaction (30 second timeout) — any per-verdict failure rolls back the entire batch and returns `failedProposalId` so the UI can highlight the offending row inline.

The path parameter `id` is the attachment ID (the chat attachment that produced the proposals), not the apply ID.

### Path Parameters

| Parameter | Type   | Description                                                              |
| --------- | ------ | ------------------------------------------------------------------------ |
| `id`      | string | The chat attachment ID — the document the proposals were extracted from. |

### Request Body

<ParamField body="conversationId" type="string" required>
  AI conversation id the proposals were generated in. Used for audit linkage.
</ParamField>

<ParamField body="verdicts" type="array" required>
  Non-empty array of `{ proposalId, verdict }` items. `verdict` is one of `"create-new"`, `"merge-into-existing"`, or `"skip"`.
</ParamField>

#### Verdict semantics

| Verdict               | Behavior                                                                                                                  |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `create-new`          | Insert a new entity from the proposal payload. The new entity ID is recorded in the apply record so revert can delete it. |
| `merge-into-existing` | Update an existing entity. The field-level diff is recorded in the audit log; revert does NOT undo merges.                |
| `skip`                | No entity write. A skip-digest row is recorded so the same proposal does not reappear in future extracts.                 |

### Response

Returns one of two response shapes — a success body when the apply commits, and a rollback body when any per-verdict failure aborts the transaction.

```json theme={null}
{
  "ok": true,
  "applyId": "apl_abc123",
  "createdEntityIds": [
    { "entityType": "offer", "entityId": "off_xyz" }
  ],
  "mergedEntityIds": [
    { "entityType": "creative", "entityId": "crv_def" }
  ],
  "skippedProposalIds": ["prop_111", "prop_222"]
}
```

Rollback response:

```json theme={null}
{
  "ok": false,
  "status": "rolled_back",
  "failedProposalId": "prop_888",
  "errorMessage": "creative.templateType is required",
  "errorField": "templateType"
}
```

### Status codes

| Code | When                                                  |
| ---- | ----------------------------------------------------- |
| 201  | Apply succeeded                                       |
| 400  | Invalid JSON body or schema validation failure        |
| 401  | Caller is not authenticated                           |
| 403  | Caller is not `admin` or `editor`                     |
| 422  | Per-verdict failure rolled back the whole transaction |
| 500  | Unexpected error during apply                         |

### Honest limit

Apply itself does not call the LLM — the customer cost was already incurred at extract time. See [AI Document Import](/ai-ml/ai-document-import).

### Roles

admin, editor

***

## POST /api/v1/ai/imports/{id}/revert

Soft-revert an apply: deletes the entities that were created by the apply, leaves merges as-is, and refuses to cascade-delete a created entity that now has child references. Orphaned refusals are surfaced in the response so the operator can resolve them manually.

The path parameter `id` is the apply ID returned from the apply call — NOT the attachment ID.

### Path Parameters

| Parameter | Type   | Description                                 |
| --------- | ------ | ------------------------------------------- |
| `id`      | string | The apply ID from the prior apply response. |

### Request Body

None — revert is a single transactional sweep keyed by the apply ID in the path.

### Response

```json theme={null}
{
  "ok": true,
  "applyId": "apl_abc123",
  "deletedEntityIds": [
    { "entityType": "offer", "entityId": "off_xyz" }
  ],
  "skippedMerges": [
    { "entityType": "creative", "entityId": "crv_def" }
  ],
  "orphanedRefusals": [
    {
      "entityType": "offer",
      "entityId": "off_old",
      "reason": "Foreign key constraint: 14 child interactionHistory rows reference this offer"
    }
  ]
}
```

<ResponseField name="deletedEntityIds" type="array">
  Entities that were created by the apply and successfully deleted. Each entry is `{ entityType, entityId }`.
</ResponseField>

<ResponseField name="skippedMerges" type="array">
  Entities that were merge-updated by the apply. Revert does NOT undo these — operators do field-level undo manually against the audit log field-diff trail.
</ResponseField>

<ResponseField name="orphanedRefusals" type="array">
  Created entities that now have child references and cannot be cascade-deleted. The `reason` field contains the truncated FK error message.
</ResponseField>

### Status codes

| Code      | When                                             |
| --------- | ------------------------------------------------ |
| 200       | Revert succeeded                                 |
| 401 / 403 | Caller is not `admin` or `editor`                |
| 404       | The apply ID does not match a row for the tenant |
| 409       | Apply was already reverted                       |
| 500       | Unexpected error during revert                   |

### Roles

admin, editor

***

## Role Summary

| Endpoint                                | Method | Allowed Roles         |
| --------------------------------------- | ------ | --------------------- |
| `/api/v1/ai/chat`                       | POST   | admin, editor, viewer |
| `/api/v1/ai/conversations`              | GET    | session auth          |
| `/api/v1/ai/conversations`              | POST   | session auth          |
| `/api/v1/ai/conversations/{id}`         | GET    | session auth          |
| `/api/v1/ai/conversations/{id}`         | DELETE | admin, editor         |
| `/api/v1/ai/analyze/{type}`             | POST   | admin, editor         |
| `/api/v1/ai/analyzer-settings`          | GET    | admin, editor, viewer |
| `/api/v1/ai/analyzer-settings`          | PUT    | admin                 |
| `/api/v1/ai/intelligence`               | POST   | admin, editor, viewer |
| `/api/v1/ai/parse-rule`                 | POST   | admin, editor         |
| `/api/v1/ai/recommendations`            | GET    | admin, editor, viewer |
| `/api/v1/ai/recommendations`            | POST   | admin, editor         |
| `/api/v1/ai/recommendations/{id}`       | GET    | admin, editor, viewer |
| `/api/v1/ai/recommendations/{id}`       | PATCH  | admin, editor         |
| `/api/v1/ai/recommendations/{id}/apply` | POST   | admin                 |
| `/api/v1/ai/ml-worker/status`           | GET    | admin, editor, viewer |
| `/api/v1/ai/config-status`              | GET    | admin, editor, viewer |
| `/api/v1/ai/imports/{id}/apply`         | POST   | admin, editor         |
| `/api/v1/ai/imports/{id}/revert`        | POST   | admin, editor         |

See also: [AI Assistant](/ai-ml/ai-assistant) | [AI Insights](/ai-ml/ai-insights) | [AI Configuration](/ai-ml/ai-configuration) | [AI Document Import](/ai-ml/ai-document-import) | [MCP Server](/integrations/mcp)
