Drift Detection
Log production predictions and ground truth, create reference baselines, run drift analysis, and configure alerts. The drift detection subsystem powers the MLOps observability layer with CBPE-calibrated confidence tracking.
All endpoints require authentication via X-API-Key header and the appropriate scope.
Prediction Object
{
"predictionId": "pred_abc123",
"modelId": "model_xyz",
"modelVersion": "1.2.0",
"features": { "age": 42, "income": 75000 },
"prediction": "approved",
"probabilities": [0.12, 0.88],
"entityId": "customer_456",
"latencyMs": 23,
"timestamp": "2026-05-16T10:30:00Z"
}
Baseline Object
{
"baselineId": "baseline_001",
"modelId": "model_xyz",
"version": "v1-training",
"active": true,
"sampleSize": 10000,
"featureData": { "age": [25, 30, 42], "income": [50000, 60000, 75000] },
"targetData": { "type": "classification", "values": ["approved", "denied"] },
"createdAt": "2026-05-01T08:00:00Z"
}
POST /api/v1/drift/predictions
Log a production prediction for drift detection. Classification predictions MUST include a probabilities array — its max value is stored as the canonical confidence score used by CBPE. Regression predictions skip confidence capture.
Scope: mlops:write
Request Body
{
"modelId": "model_xyz",
"features": { "age": 42, "income": 75000 },
"prediction": "approved",
"probabilities": [0.12, 0.88],
"entityId": "customer_456",
"modelVersion": "1.2.0",
"latencyMs": 23
}
| Field | Type | Required | Description |
|---|---|---|---|
modelId | string | Yes | Model ID from the model registry |
features | object | Yes | Input feature values as key-value pairs |
prediction | string | Yes | Model prediction output |
entityId | string | No | Business entity ID for ground truth matching |
probabilities | array | No | Per-class probability scores (required for classification) |
modelVersion | string | No | Model version string |
latencyMs | number | No | Prediction latency in milliseconds |
Response 201 Created
{
"predictionId": "pred_abc123"
}
GET /api/v1/drift/predictions
List prediction logs for a model.
Scope: mlops:read
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
modelId | string | Yes | Model ID |
limit | number | No | Max results (default 100) |
offset | number | No | Pagination offset |
startDate | string | No | Filter start date (ISO 8601) |
endDate | string | No | Filter end date (ISO 8601) |
hasGroundTruth | string | No | Filter by ground truth match status (true/false) |
Response 200 OK
{
"predictions": [
{
"predictionId": "pred_abc123",
"modelId": "model_xyz",
"features": { "age": 42 },
"prediction": "approved",
"timestamp": "2026-05-16T10:30:00Z"
}
],
"total": 248
}
GET /api/v1/drift/predictions/unmatched
List predictions that have not yet been matched with ground truth.
Scope: mlops:read
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
modelId | string | Yes | Model ID |
limit | number | No | Max results (default 100) |
Response 200 OK
{
"predictions": [
{
"predictionId": "pred_abc123",
"entityId": "customer_456",
"timestamp": "2026-05-16T10:30:00Z"
}
]
}
POST /api/v1/drift/ground-truth
Add a single ground truth record, matched with a prediction by entityId.
Scope: mlops:write
Request Body
{
"modelId": "model_xyz",
"entityId": "customer_456",
"actualOutcome": "approved",
"outcomeTimestamp": "2026-05-16T12:00:00Z"
}
| Field | Type | Required | Description |
|---|---|---|---|
modelId | string | Yes | Model ID |
entityId | string | Yes | Business entity ID |
actualOutcome | string | Yes | The actual outcome/label |
outcomeTimestamp | string | No | When the outcome was observed (ISO 8601) |
Response 201 Created
{
"groundTruthId": "gt_def456"
}
POST /api/v1/drift/ground-truth/batch
Bulk upload ground truth records.
Scope: mlops:write
Request Body
{
"modelId": "model_xyz",
"records": [
{ "entityId": "customer_456", "actualOutcome": "approved" },
{ "entityId": "customer_789", "actualOutcome": "denied", "outcomeTimestamp": "2026-05-16T12:00:00Z" }
]
}
| Field | Type | Required | Description |
|---|---|---|---|
modelId | string | Yes | Model ID |
records | array | Yes | Array of {entityId, actualOutcome, outcomeTimestamp?} objects |
Response 200 OK
{
"inserted": 2,
"matched": 2
}
POST /api/v1/drift/baselines
Create a reference baseline from training data. Provide labeledPredictions for classification models to capture real baseline confidences, labels and accuracy — this unlocks calibrated CBPE and prediction-drift comparison. No synthetic fallbacks.
Scope: mlops:write
Request Body
{
"modelId": "model_xyz",
"version": "v1-training",
"featureData": { "age": [25, 30, 42], "income": [50000, 60000, 75000] },
"targetData": { "type": "classification", "values": ["approved", "denied"] },
"labeledPredictions": {
"probabilities": [[0.1, 0.9], [0.7, 0.3]],
"predictedLabels": ["approved", "denied"],
"actualLabels": ["approved", "denied"]
}
}
| Field | Type | Required | Description |
|---|---|---|---|
modelId | string | Yes | Model ID |
version | string | Yes | Baseline version tag |
featureData | object | Yes | { featureName: [values...] } — training samples per feature |
targetData | object | No | { type: "classification"|"regression", values: [...] } |
labeledPredictions | object | No | { probabilities, predictedLabels, actualLabels } aligned 1:1 — enables CBPE calibration and baseline accuracy |
Response 201 Created
{
"baselineId": "baseline_001"
}
GET /api/v1/drift/baselines
List all reference baselines for a model.
Scope: mlops:read
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
modelId | string | Yes | Model ID |
Response 200 OK
{
"baselines": [
{
"baselineId": "baseline_001",
"version": "v1-training",
"active": true,
"sampleSize": 10000
}
]
}
GET /api/v1/drift/baselines/active
Get the currently active reference baseline for a model.
Scope: mlops:read
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
modelId | string | Yes | Model ID |
Response 200 OK
Returns the active Baseline object.
PUT /api/v1/drift/baselines/:id/activate
Set a specific baseline as the active one for drift analysis.
Scope: mlops:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Baseline ID to activate |
Request Body
{
"modelId": "model_xyz"
}
| Field | Type | Required | Description |
|---|---|---|---|
modelId | string | Yes | Model ID |
Response 200 OK
{
"activated": true,
"baselineId": "baseline_001"
}
POST /api/v1/drift/analyze
Run drift analysis for a model. Spawns a K8s job that runs the full algorithm registry (univariate, multivariate, performance, streaming, custom) against recent predictions.
Scope: mlops:write
Request Body
{
"modelId": "model_xyz",
"windowDays": 7,
"windowStart": "2026-05-09T00:00:00Z",
"windowEnd": "2026-05-16T00:00:00Z"
}
| Field | Type | Required | Description |
|---|---|---|---|
modelId | string | Yes | Model ID |
windowDays | number | No | Days of predictions to analyze |
windowStart | string | No | Analysis window start (ISO 8601) |
windowEnd | string | No | Analysis window end (ISO 8601) |
Response 200 OK
{
"jobId": "drift_job_001",
"status": "running"
}
GET /api/v1/drift/analyze/:id/status
Check the status of a drift analysis job.
Scope: mlops:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Job ID |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
modelId | string | Yes | Model ID |
Response 200 OK
{
"jobId": "drift_job_001",
"status": "succeeded",
"startedAt": "2026-05-16T10:00:00Z",
"completedAt": "2026-05-16T10:05:00Z"
}
GET /api/v1/drift/results/latest
Get the most recent drift analysis result for a model.
Scope: mlops:read
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
modelId | string | Yes | Model ID |
Response 200 OK
{
"resultId": "result_001",
"modelId": "model_xyz",
"status": "warning",
"algorithms": {
"ks_test": { "drift": true, "score": 0.18 },
"psi": { "drift": false, "score": 0.05 }
},
"createdAt": "2026-05-16T10:05:00Z"
}
GET /api/v1/drift/results
List drift analysis result history for a model.
Scope: mlops:read
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
modelId | string | Yes | Model ID |
limit | number | No | Max results (default 50) |
status | string | No | Filter by drift status: ok, warning, alert |
Response 200 OK
{
"results": [
{
"resultId": "result_001",
"status": "warning",
"createdAt": "2026-05-16T10:05:00Z"
}
]
}
GET /api/v1/drift/alerts
Get the drift alert configuration for a model, including per-algorithm overrides and schedule.
Scope: mlops:read
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
modelId | string | Yes | Model ID |
Response 200 OK
{
"modelId": "model_xyz",
"enabled": true,
"algorithms": {
"enabled": ["ks_test", "psi"],
"overrides": { "ks_test": { "warningThreshold": 0.1, "alertThreshold": 0.2 } }
},
"accuracyDropWarning": 0.05,
"accuracyDropAlert": 0.10,
"notifications": { "email": true, "recipients": ["mlops@example.com"] },
"schedule": { "enabled": true, "frequency": "daily", "timeWindowDays": 7 }
}
PUT /api/v1/drift/alerts
Update drift alert configuration. Enable/disable algorithms, override thresholds per algorithm, configure accuracy-drop thresholds, notifications, and schedule. The backend scheduler reads schedule.enabled/frequency directly — no separate schedule endpoint.
Scope: mlops:write
Request Body
{
"modelId": "model_xyz",
"enabled": true,
"algorithms": {
"enabled": ["ks_test", "psi"],
"overrides": { "ks_test": { "warningThreshold": 0.1, "alertThreshold": 0.2 } }
},
"accuracyDropWarning": 0.05,
"accuracyDropAlert": 0.10,
"minBaselineSampleSize": 30,
"notifications": { "email": true, "recipients": ["mlops@example.com"] },
"schedule": { "enabled": true, "frequency": "daily", "timeWindowDays": 7 }
}
| Field | Type | Required | Description |
|---|---|---|---|
modelId | string | Yes | Model ID |
enabled | boolean | No | Enable/disable alerts |
algorithms | object | No | { enabled: string[], overrides: { [algo]: { warningThreshold, alertThreshold, params } } } |
accuracyDropWarning | number | No | Warning threshold for accuracy drop (default 0.05) |
accuracyDropAlert | number | No | Alert threshold for accuracy drop (default 0.10) |
minBaselineSampleSize | number | No | Minimum required baseline sampleSize (default 30) |
notifications | object | No | { email, slack?, recipients[] } |
schedule | object | No | { enabled, frequency: hourly|daily|weekly, timeWindowDays } |
Response 200 OK
{
"updated": true
}
GET /api/v1/drift/performance
Get model performance history over time.
Scope: mlops:read
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
modelId | string | Yes | Model ID |
windowDays | number | No | Lookback window in days (default 30) |
granularity | string | No | Time bucket: hourly, daily, weekly |
Response 200 OK
{
"history": [
{ "timestamp": "2026-05-15T00:00:00Z", "accuracy": 0.92, "sampleSize": 480 },
{ "timestamp": "2026-05-16T00:00:00Z", "accuracy": 0.89, "sampleSize": 512 }
]
}