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

# Algorithm Models

> Manage scoring models (scorecard, bayesian, logistic_regression, gradient_boosted, thompson_bandit, epsilon_greedy, neural_cf, online_learner, external_endpoint). Train from real outcomes, score customers, score offer sets, upgrade model tiers, reset learning, and view evolution history.

<Frame caption="The Algorithm Models page.">
  <img src="https://mintcdn.com/kaireonai/l-jsUQlUEuA3B6hG/images/screenshots/algorithm-models-list.png?fit=max&auto=format&n=l-jsUQlUEuA3B6hG&q=85&s=1e72c81494a95ea2a7b028d6d318eef8" alt="Algorithm Models list view in the Algorithms module" width="1440" height="900" data-path="images/screenshots/algorithm-models-list.png" />
</Frame>

## GET /api/v1/algorithm-models

List all algorithm models. Supports cursor-based pagination.

### Response

```json theme={null}
{
  "data": [
    {
      "id": "model_001",
      "key": "propensity-credit-card",
      "name": "Credit Card Propensity",
      "modelType": "bayesian",
      "status": "active",
      "version": 3,
      "trainingSamples": 12500,
      "metrics": { "auc": 0.78, "accuracy": 0.82 },
      "lastTrainedAt": "2026-03-14T06:00:00.000Z",
      "createdAt": "2026-01-20T10:00:00.000Z"
    }
  ],
  "pagination": {
    "total": 5,
    "hasMore": false,
    "limit": 50,
    "cursor": null
  }
}
```

***

## POST /api/v1/algorithm-models

Create a new algorithm model.

### Request Body

| Field             | Type           | Required | Description                                                                                                                                                                                                                                                                                                |
| ----------------- | -------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `key`             | string         | Yes      | Unique model key                                                                                                                                                                                                                                                                                           |
| `name`            | string         | Yes      | Display name                                                                                                                                                                                                                                                                                               |
| `modelType`       | string         | Yes      | One of: `scorecard`, `bayesian`, `logistic_regression`, `gradient_boosted`, `thompson_bandit`, `epsilon_greedy`, `neural_cf`, `online_learner`, `external_endpoint`.                                                                                                                                       |
| `description`     | string         | No       | Description                                                                                                                                                                                                                                                                                                |
| `status`          | string         | No       | Default: `"draft"`                                                                                                                                                                                                                                                                                         |
| `config`          | object         | No       | Model-specific configuration                                                                                                                                                                                                                                                                               |
| `targetField`     | string         | No       | Target variable field name                                                                                                                                                                                                                                                                                 |
| `targetSchemaKey` | string         | No       | Schema key containing the target                                                                                                                                                                                                                                                                           |
| `predictors`      | array          | No       | Predictor feature definitions                                                                                                                                                                                                                                                                              |
| `metrics`         | object         | No       | Current model metrics (e.g., `{ auc, accuracy }`)                                                                                                                                                                                                                                                          |
| `metricsHistory`  | array          | No       | Historical metrics snapshots                                                                                                                                                                                                                                                                               |
| `modelState`      | object         | No       | Model state (weights, priors, embeddings, etc.)                                                                                                                                                                                                                                                            |
| `learningConfig`  | object         | No       | **Reserved metadata — not read by any trainer.** Accepted and persisted as-is, but no scoring/training code path reads it. Hyperparameters that actually affect training or scoring (learning rate, epochs, `priorAlpha`, `epsilon`, etc.) are type-specific per `modelType` and live in `config` instead. |
| `autoLearn`       | boolean        | No       | Master switch for scheduled retraining. Default: `false`. When `false`, the scheduled-retrains cron skips this model entirely (bandits and online-learners ignore this flag — they always learn on every respond).                                                                                         |
| `learnMode`       | string         | No       | One of `"none"`, `"incremental"`, `"scheduled"`, `"both"`. Default: `"none"`. `"scheduled"` enables the cron-driven offline retrain path; `"incremental"` is the per-respond update path (only meaningful for bayesian — bandits/online-learners do this regardless).                                      |
| `learnSchedule`   | string \| null | No       | Cron expression (`"0 3 * * *"`) or interval shorthand (`"15m"`, `"6h"`, `"24h"`, `"7d"`). Default: `null` (→ 24h when `learnMode` is `"scheduled"`).                                                                                                                                                       |

<Note>
  `outcomeWeights`, `interactionFeatures`, and `evolutionConfig` are **not** accepted on create — the create schema silently drops them. Set them via `PUT /api/v1/algorithm-models/{id}` after the model exists.
</Note>

<Note>
  **Defaults are intentionally conservative.** A POST with no overrides creates a fully inert model: `status: "draft"`, `registryStatus: "draft"`, `autoLearn: false`, `learnMode: "none"`. It exists in the DB but doesn't score traffic, isn't a champion, and won't retrain. To make it actually do something see the explicit 4-step path in [Model lifecycle](/ai-ml/model-lifecycle).
</Note>

### Validation

All fields are validated via Zod schemas:

* `key`: 1-255 characters, must be unique per tenant.
* `name`: 1-255 characters.
* `modelType`: Must be one of the nine enum values listed above.
* `status`: Default `draft`. One of: `draft`, `active`, `paused`, `archived`.
* `config`, `metrics`, `modelState`, `learningConfig`: JSON objects (max 100 keys each; keys named `__proto__`, `constructor`, or `prototype` are rejected). These limits apply to every JSON-object field across the API.
* `predictors`, `metricsHistory`: JSON arrays (max 500 items each).

### Example

```bash theme={null}
curl -X POST https://playground.kaireonai.com/api/v1/algorithm-models \
  -H "Content-Type: application/json" \
  -H "X-Tenant-Id: my-tenant" \
  -d '{
    "key": "propensity-credit-card",
    "name": "Credit Card Propensity",
    "modelType": "scorecard",
    "targetField": "converted",
    "targetSchemaKey": "customers",
    "predictors": ["income", "credit_score", "tenure_months"]
  }'
```

### Error codes

| Code  | Reason                                                                                                                       |
| ----- | ---------------------------------------------------------------------------------------------------------------------------- |
| `400` | Validation error (missing key or name, invalid modelType), or duplicate key (a model with this key and type already exists). |
| `415` | `Content-Type` is not `application/json`.                                                                                    |

**Response:** `201 Created`

***

## GET /api/v1/algorithm-models/{id}

Get model details including version history.

**Response:** `200 OK` with the model object and `versions` array.

***

## PUT /api/v1/algorithm-models/{id}

Update a model's configuration, status, metrics, or learning config.

### Request Body

All fields are optional. Only provided fields are updated.

| Field                 | Type   | Description                                                                                                                                                                                                                                                                                                  |
| --------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `name`                | string | Updated name                                                                                                                                                                                                                                                                                                 |
| `description`         | string | Updated description                                                                                                                                                                                                                                                                                          |
| `status`              | string | Updated status                                                                                                                                                                                                                                                                                               |
| `config`              | object | Updated model config                                                                                                                                                                                                                                                                                         |
| `targetField`         | string | Updated target field                                                                                                                                                                                                                                                                                         |
| `predictors`          | array  | Updated predictor list                                                                                                                                                                                                                                                                                       |
| `metrics`             | object | Updated metrics                                                                                                                                                                                                                                                                                              |
| `modelState`          | object | Updated model state (weights, coefficients)                                                                                                                                                                                                                                                                  |
| `learningConfig`      | object | Updated — but reserved metadata, not read by any trainer; use `config` to actually affect training/scoring                                                                                                                                                                                                   |
| `outcomeWeights`      | object | Map from outcome-type key to signed numeric weight. `null` falls back to `+1` for any outcome classified `"positive"` and `−1` for `"negative"`. **Misconfiguration silently inverts learning** — see [Model lifecycle: Configure outcome weights](/ai-ml/model-lifecycle#step-4-configure-outcome-weights). |
| `interactionFeatures` | object | Interaction feature configuration                                                                                                                                                                                                                                                                            |
| `evolutionConfig`     | object | Auto-evolution tier thresholds                                                                                                                                                                                                                                                                               |

**Response:** `200 OK`

***

## DELETE /api/v1/algorithm-models?id={modelId}

Hard-deletes a model permanently. Unlike decisioning gates and contact policies, algorithm models use **hard delete** (the record is physically removed from the database).

### Query parameters

| Parameter | Required | Type   | Description         |
| --------- | -------- | ------ | ------------------- |
| `id`      | **Yes**  | string | Model ID to delete. |

### Error codes

| Code  | Reason                        |
| ----- | ----------------------------- |
| `400` | Missing `id` query parameter. |
| `404` | Algorithm model not found.    |

**Response:** `204 No Content`

***

## POST /api/v1/algorithm-models/{id}/score

Score a single customer against the model.

### Request Body

| Field        | Type   | Required | Description               |
| ------------ | ------ | -------- | ------------------------- |
| `attributes` | object | No       | Customer attribute vector |

### Response

The response shape depends on model type. The common field is `score`:

```json theme={null}
{
  "score": 0.73
}
```

Additional fields may be present depending on model type:

| Field          | Type   | Present for                                                                                             | Description                           |
| -------------- | ------ | ------------------------------------------------------------------------------------------------------- | ------------------------------------- |
| `score`        | number | All types                                                                                               | The computed score (0-1)              |
| `confidence`   | number | `bayesian`                                                                                              | Confidence level                      |
| `explanations` | array  | `scorecard`, `bayesian`, `logistic_regression`, `gradient_boosted`, `thompson_bandit`, `epsilon_greedy` | Per-predictor or per-offer breakdowns |

<Note>
  There is no `contributions` array in the response. Use `explanations` for per-predictor or per-offer breakdowns (available for scorecard, bayesian, logistic\_regression, gradient\_boosted, thompson\_bandit, and epsilon\_greedy model types).
</Note>

***

## POST /api/v1/algorithm-models/{id}/score-offer-set

Score a set of offers for a customer. Returns per-offer propensity scores with optional interaction history features.

### Request Body

| Field                | Type   | Required | Description                                             |
| -------------------- | ------ | -------- | ------------------------------------------------------- |
| `customerAttributes` | object | No       | Customer feature vector                                 |
| `offers`             | array  | Yes      | Array of `{ id, name?, attributes }`                    |
| `customerId`         | string | No       | If provided, fetches real interaction summaries from DB |
| `context`            | object | No       | Real-time context features                              |

### Response

Scores are returned sorted by `score` descending — the array index itself is the rank (index 0 = highest score).

```json theme={null}
{
  "modelId": "model_001",
  "modelKey": "propensity-credit-card",
  "modelType": "bayesian",
  "customerId": "CUST001",
  "count": 3,
  "scores": [
    { "offerId": "offer_001", "offerName": "Credit Card Premium", "score": 0.87, "interactionFeatures": { "impressions": 5, "positiveRate": 0.6 } },
    { "offerId": "offer_002", "offerName": "Savings Account", "score": 0.65 },
    { "offerId": "offer_003", "offerName": "Home Loan", "score": 0.42 }
  ]
}
```

<Note>
  There is no `rank` field in the response. The array order IS the ranking — index 0 is the highest-scored offer. If `customerId` is provided, `interactionFeatures` will be populated from the customer's recorded interaction history (impressions, click rate, conversion rate per offer).
</Note>

***

## POST /api/v1/algorithm-models/{id}/train

Train (or retrain) a model using the per-model training route. Creates a new version snapshot and updates metrics history. Requires at least 50 interaction records in the tenant.

### Response

**200 OK** — Returns the updated model with incremented `version`, refreshed `metrics`, and `status` set to `"active"`.

**422 Unprocessable Entity** — Returned when the training request is well-formed but can't proceed:

| `error.code`             | Cause                                                                                                                 |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------- |
| `INSUFFICIENT_DATA`      | Fewer than 50 interaction records to train from                                                                       |
| `NO_PREDICTORS_SELECTED` | A `gradient_boosted` model has no selected predictors — add at least one before training                              |
| `MODEL_NOT_TRAINABLE`    | The model type is scored via an external source and is not trained in-platform (`external_endpoint`, `onnx_imported`) |

**503 Service Unavailable** — `error.code: "ML_WORKER_UNAVAILABLE"`, `retryable: true`. Returned when a `gradient_boosted` model is trained but the Python [ML Worker](/self-host/deploy/ml-worker) (`ML_WORKER_URL`) is unreachable. The error message is surfaced verbatim ("Training requires the Python ml-worker service. Scoring remains available on the last trained model."), so the model keeps scoring on its previously-trained state. All in-process model types (`bayesian`, `scorecard`, `thompson_bandit`, `neural_cf`, `online_learner`) train without the worker and are unaffected.

<Info>
  Training requires interaction data recorded via the [Respond API](/api-reference/respond). New tenants with no interaction history will receive a 422 until enough outcomes are recorded. Models can still be used for scoring without training — scorecard models use bin-based rules, and other model types use default priors.
</Info>

***

## POST /api/v1/algorithm-models/train

Bulk-train a model from real interaction outcomes. This is the recommended training endpoint — it replays the tenant's recorded outcomes through the model's training routine, updates `lastTrainedAt`, increments `version`, and creates a model version snapshot for rollback.

### Supported model types

All built-in model types are supported:

| Type                  | Training method                                                                                                                                             |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `scorecard`           | Evaluates rule config against outcome data, computes confusion matrix metrics                                                                               |
| `bayesian`            | Replays outcomes through Bayesian updater (Naive Bayes with Laplace smoothing)                                                                              |
| `logistic_regression` | Linear classifier trained in-process with SGD + L1/L2 regularization                                                                                        |
| `gradient_boosted`    | LightGBM tree ensemble trained in the Python [ML Worker](/self-host/deploy/ml-worker); scored in-process in Node. Requires `ML_WORKER_URL` to be configured |
| `thompson_bandit`     | Computes per-offer conversion rates as evaluation metrics                                                                                                   |
| `epsilon_greedy`      | Computes per-offer conversion rates as evaluation metrics                                                                                                   |
| `neural_cf`           | Trains two-tower embedding model with mini-batch SGD (binary cross-entropy)                                                                                 |
| `online_learner`      | Streams through interactions one-by-one with SGD logistic regression                                                                                        |

### Request Body

| Field     | Type   | Required | Description                 |
| --------- | ------ | -------- | --------------------------- |
| `modelId` | string | Yes      | Algorithm model ID to train |

### Response

```json theme={null}
{
  "modelId": "model_001",
  "modelType": "bayesian",
  "sampleCount": 1250,
  "metrics": {
    "accuracy": 0.74,
    "precision": 0.68,
    "recall": 0.71,
    "f1": 0.69,
    "auc": 0.76
  },
  "status": "success"
}
```

### Error responses

Failures are classified rather than collapsed into a single status. The body carries `error.code` and (for transient conditions) `error.retryable`.

| Status | `error.code`             | Condition                                                                                                                                             |
| ------ | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | —                        | Missing `modelId`                                                                                                                                     |
| `422`  | `INSUFFICIENT_DATA`      | Fewer than 50 interactions in tenant                                                                                                                  |
| `422`  | `NO_PREDICTORS_SELECTED` | `gradient_boosted` model with no selected predictors                                                                                                  |
| `422`  | `MODEL_NOT_TRAINABLE`    | Model type is scored externally, not trained in-platform (`external_endpoint`, `onnx_imported`)                                                       |
| `503`  | `ML_WORKER_UNAVAILABLE`  | `gradient_boosted` training but the ML Worker (`ML_WORKER_URL`) is unreachable — `retryable: true`; the model keeps scoring on its last trained state |
| `500`  | `INTERNAL_ERROR`         | Genuine unexpected fault                                                                                                                              |

***

## POST /api/v1/algorithm-models/{id}/upgrade

Upgrade a model to the next tier along the progression `scorecard → bayesian → logistic_regression → gradient_boosted`. Optionally creates a champion/challenger experiment. Upgrading to `gradient_boosted` requires the [ML Worker](/self-host/deploy/ml-worker) to be reachable.

### Request Body

| Field              | Type    | Required | Description                                                  |
| ------------------ | ------- | -------- | ------------------------------------------------------------ |
| `createExperiment` | boolean | No       | If `true`, auto-creates a 50/50 experiment. Default: `false` |

### Response

```json theme={null}
{
  "upgraded": { "id": "model_002", "modelType": "bayesian", "status": "draft" },
  "experiment": { "id": "exp_001", "name": "Credit Card Propensity: Champion vs Bayesian Upgrade" }
}
```

**Response:** `201 Created`

***

## GET /api/v1/algorithm-models/{id}/evolution-history

View model evolution config, progress toward the next tier, and a timeline of version transitions.

### Response

```json theme={null}
{
  "modelId": "model_001",
  "modelKey": "propensity-credit-card",
  "currentModelType": "bayesian",
  "evolutionConfig": {
    "autoEvolve": true,
    "currentTier": "bayesian",
    "targetTier": "gradient_boosted",
    "thresholds": {
      "gradientBoostedSamples": 20000,
      "gradientBoostedAuc": 0.80
    }
  },
  "progress": {
    "nextTier": "gradient_boosted",
    "progressPct": 83,
    "readyToEvolve": false,
    "details": {
      "samplesRequired": 20000,
      "samplesActual": 13600,
      "aucRequired": 0.80,
      "aucActual": 0.78
    }
  },
  "timeline": [
    { "version": 3, "modelType": "bayesian", "metrics": { "auc": 0.78 }, "createdAt": "2026-03-14T06:00:00.000Z" }
  ]
}
```

***

## POST /api/v1/algorithm-models/{id}/reset-offer

Reset or pause adaptive learning for a specific offer, category, channel, or globally.

### Request Body

| Field        | Type   | Required | Description                                                   |
| ------------ | ------ | -------- | ------------------------------------------------------------- |
| `action`     | string | No       | `"reset"` (default), `"pause"`, or `"resume"`                 |
| `scope`      | string | No       | `"offer"` (default), `"category"`, `"channel"`, or `"global"` |
| `offerId`    | string | No       | Offer to reset/pause (convenience alias for scopeId)          |
| `categoryId` | string | No       | Category to reset (resets all offers in category)             |
| `channelId`  | string | No       | Channel to reset                                              |
| `resetTo`    | string | No       | `"category_prior"` (default), `"global_prior"`, or `"zero"`   |
| `reason`     | string | No       | Audit trail reason                                            |

### Response

```json theme={null}
{
  "modelId": "model_001",
  "scope": "offer",
  "scopeId": "offer_auto_renewal",
  "action": "reset",
  "resetTo": "category_prior",
  "previousEvidence": 3241,
  "newPrior": 0.281,
  "reason": "Fixed qualification rules"
}
```

See [Adaptive Learning](/ai-ml/adaptive-learning) for full details on hierarchical learning, evidence decay, and PRIE scoring.

***

## POST /api/v1/algorithm-models/{id}/reset-learning

Reset a model's learned state back to fresh defaults. Creates a pre-reset version snapshot, clears metrics/metricsHistory, resets trainingSamples to 0, and sets status to `"draft"`.

<Warning>
  Scorecard models have no learned state — calling reset-learning on a scorecard returns `400 Bad Request`.
</Warning>

### Response

**200 OK** — Returns the model with cleared state:

```json theme={null}
{
  "id": "model_001",
  "status": "draft",
  "version": 4,
  "metrics": {},
  "metricsHistory": [],
  "trainingSamples": 0,
  "lastTrainedAt": null
}
```

***

## GET /api/v1/algorithm-models/{id}/adaptations

List the model's per-scope `ModelAdaptation` rows (the hierarchical offer → category → channel → global posteriors that drive adaptive learning). Useful for ops debugging and as the data source for the Model Health "Adaptations" panel.

### Query parameters

| Parameter | Required | Type   | Description                                                                    |
| --------- | -------- | ------ | ------------------------------------------------------------------------------ |
| `scope`   | No       | string | Filter to one scope: `offer`, `category`, `channel`, `direction`, or `global`. |
| `scopeId` | No       | string | Exact `scopeId` to look up. Only applied when `scope` is also set.             |

### Response

Rows are grouped under `byScope`, keyed by scope name. Each row carries `scope`, `scopeId`, `positives`, `negatives`, `evidence`, `positiveRate`, `paused`, `decayedAt`, and `updatedAt`.

```json theme={null}
{
  "modelId": "model_001",
  "modelKey": "propensity-credit-card",
  "totalRows": 12,
  "scopes": ["category", "global", "offer"],
  "byScope": {
    "offer": [
      { "scope": "offer", "scopeId": "offer_001", "positives": 340, "negatives": 812, "evidence": 1152, "positiveRate": 0.295, "paused": false, "decayedAt": null, "updatedAt": "2026-03-14T06:00:00.000Z" }
    ],
    "category": [
      { "scope": "category", "scopeId": "cat_cards", "positives": 1204, "negatives": 3380, "evidence": 4584, "positiveRate": 0.263, "paused": false, "decayedAt": null, "updatedAt": "2026-03-14T06:00:00.000Z" }
    ],
    "global": [
      { "scope": "global", "scopeId": "", "positives": 4102, "negatives": 11890, "evidence": 15992, "positiveRate": 0.256, "paused": false, "decayedAt": null, "updatedAt": "2026-03-14T06:00:00.000Z" }
    ]
  }
}
```

### Error codes

| Code  | Reason                               |
| ----- | ------------------------------------ |
| `404` | Algorithm model not found in tenant. |

***

## GET /api/v1/algorithm-models/{id}/uplift

Per-offer Conditional Average Treatment Effect (CATE) estimates for one customer, using T-learner or X-learner metalearners over the model's stored adaptations (marginal mode) or per-row-fitted base learners (fitted mode).

### Query parameters

| Parameter    | Required | Type   | Description                                                                                                                     |
| ------------ | -------- | ------ | ------------------------------------------------------------------------------------------------------------------------------- |
| `customerId` | **Yes**  | string | Customer to estimate uplift for.                                                                                                |
| `method`     | No       | string | `t_learner` (default) or `x_learner`.                                                                                           |
| `offerIds`   | No       | string | Comma-separated offer IDs. Defaults to all `active` offers in the tenant.                                                       |
| `mode`       | No       | string | `marginal` (default) uses offer-scope vs category-scope adaptations; `fitted` fits per-row T/X-learners on interaction history. |
| `channelId`  | No       | string | Score-time channel context (fitted mode).                                                                                       |
| `direction`  | No       | string | `inbound` (default) or `outbound`.                                                                                              |

### Response

```json theme={null}
{
  "customerId": "CUST001",
  "modelId": "model_001",
  "modelKey": "propensity-credit-card",
  "method": "t_learner",
  "mode": "marginal",
  "scoreContext": { "channelId": null, "direction": "inbound" },
  "offers": [
    {
      "offerId": "offer_001",
      "offerName": "Credit Card Premium",
      "tau": 0.0421,
      "muT": 0.2951,
      "muC": 0.253,
      "segment": "persuadable",
      "confidence": 0.63,
      "evidenceOffer": 1152,
      "evidenceCategory": 4584,
      "modeUsed": "marginal",
      "trainedN": null,
      "xLearner": null
    }
  ],
  "ate": { "ate": 0.0421, "n": 1, "sd": 0 },
  "classify": { },
  "trainingRowsLoaded": 0,
  "cacheHit": false,
  "note": "Marginal CATE from offer-scope vs category-scope ModelAdaptation. Add `?mode=fitted` for per-row T-learner / X-learner training."
}
```

`segment` is one of `persuadable`, `sure_thing`, `lost_cause`, `sleeping_dog`, or `uncertain`. When no offers are in scope, `offers` is empty and a `note` explains why.

### Error codes

| Code  | Reason                                                                  |
| ----- | ----------------------------------------------------------------------- |
| `400` | Missing `customerId`, or `method`/`mode` not one of the allowed values. |
| `404` | Algorithm model not found in tenant.                                    |

***

## GET /api/v1/models/{id}/registry

Read the model's current registry metadata: status, family label, last promotion timestamp, and snapshot metrics. The lookup is filtered by tenant.

<Note>
  This endpoint lives at `/api/v1/models/[id]/registry` — note the `models` prefix (not `algorithm-models`). It is the model-registry promotion surface, separate from the model CRUD routes documented above.
</Note>

### Path Parameters

| Parameter | Type   | Description                                     |
| --------- | ------ | ----------------------------------------------- |
| `id`      | string | Algorithm model ID — must belong to the tenant. |

### Response

```json theme={null}
{
  "modelId": "model_001",
  "modelName": "Credit Card Propensity",
  "modelType": "bayesian",
  "registry": {
    "status": "champion",
    "family": "credit-card-propensity",
    "promotedAt": "2026-04-22T10:00:00.000Z",
    "promotedBy": "alice@example.com",
    "previousStatus": "challenger",
    "metricsAtPromotion": { "auc": 0.79, "accuracy": 0.83 }
  }
}
```

<ResponseField name="registry.status" type="string">
  One of `draft`, `challenger`, `champion`, `archived`.
</ResponseField>

<ResponseField name="registry.family" type="string">
  Family label that groups models for the "one champion per family" invariant. Defaults to the model name when not explicitly set.
</ResponseField>

### Status codes

| Code | When                                         |
| ---- | -------------------------------------------- |
| 200  | Returns metadata                             |
| 400  | Missing `id` path segment                    |
| 401  | Caller is not authenticated                  |
| 403  | Caller is not `viewer`, `editor`, or `admin` |
| 404  | Model not found for tenant                   |

***

## POST /api/v1/models/{id}/registry

Promote or demote a model. Enforces a legal-transition matrix and the "one champion per family" invariant — promoting a model to `champion` demotes any existing champion in the same family inside the same database transaction. Every status change writes one audit-log entry (best-effort, after the core transaction commits).

### Legal status transitions

| From         | Allowed to                      |
| ------------ | ------------------------------- |
| `draft`      | `challenger`, `archived`        |
| `challenger` | `champion`, `archived`, `draft` |
| `champion`   | `archived`, `challenger`        |
| `archived`   | `draft`                         |

Any other transition is rejected with `400 Bad Request` and an error code of `invalid_transition`.

### Request Body

<ParamField body="status" type="string" required>
  Target status. One of `draft`, `challenger`, `champion`, `archived`.
</ParamField>

<ParamField body="actor" type="string">
  Operator id or system label for the audit row. Defaults to `"system"` when omitted.
</ParamField>

<ParamField body="metricsSnapshot" type="object">
  Map of `metric → number` recorded alongside the promotion (e.g., `{ auc: 0.79, accuracy: 0.83 }`). Used by the W6.2 auto-rollback guard to compare a candidate champion against the incumbent.
</ParamField>

### Response

```json theme={null}
{
  "modelId": "model_001",
  "previousStatus": "challenger",
  "newStatus": "champion",
  "demotedChampionId": "model_old_001"
}
```

<ResponseField name="demotedChampionId" type="string">
  Present only when the promotion to `champion` displaced an incumbent in the same family. The incumbent's status is set to `challenger` in the same transaction.
</ResponseField>

<ResponseField name="rollbackGuardBreaches" type="array">
  Present when the W6.2 rollback guard reported breached thresholds but the promotion was still permitted (e.g., `bypassRollbackGuard: true`). Each entry: `{ signal, value, threshold }`.
</ResponseField>

### Status codes

| Code | When                                                          |
| ---- | ------------------------------------------------------------- |
| 200  | Promotion succeeded                                           |
| 400  | Invalid body or illegal status transition                     |
| 401  | Caller is not authenticated                                   |
| 403  | Caller is not `admin`                                         |
| 404  | Model not found for tenant                                    |
| 429  | Rate limit exceeded — 60 promotions per 60 seconds per tenant |
| 500  | Unexpected error                                              |

### Roles

POST: admin only. GET: admin, editor, viewer.

***

## Roles

| Endpoint                            | Allowed Roles         |
| ----------------------------------- | --------------------- |
| `GET /algorithm-models`             | admin, editor, viewer |
| `POST /algorithm-models`            | admin, editor         |
| `PUT /algorithm-models/{id}`        | admin, editor         |
| `DELETE /algorithm-models/{id}`     | admin, editor         |
| `POST /{id}/score`                  | any authenticated     |
| `POST /{id}/score-offer-set`        | any authenticated     |
| `POST /{id}/train`                  | admin, editor         |
| `POST /algorithm-models/train`      | admin, editor         |
| `POST /{id}/upgrade`                | admin, editor         |
| `POST /{id}/reset-offer`            | admin, editor         |
| `POST /{id}/reset-learning`         | admin, editor         |
| `GET /{id}/adaptations`             | admin, editor, viewer |
| `GET /{id}/uplift`                  | admin, editor, viewer |
| `GET /{id}/evolution-history`       | any authenticated     |
| `GET /api/v1/models/{id}/registry`  | admin, editor, viewer |
| `POST /api/v1/models/{id}/registry` | admin                 |

See also: [Algorithms & Models](/ai-ml/algorithms)
