Risk models API
Ce contenu n’est pas encore disponible dans votre langue.
The Risk models API is what the Models page uses: it lists your workspace’s risk model versions, queues trainings, and moves a version between draft, shadow, live and retired.
It doesn’t score anything. Scoring happens inside real-time decisions and post-event monitoring, and the score reaches you through rules: see Using model scores in rules.
Before you start
Section titled “Before you start”| Who can call it | A signed-in person. Reading needs models.view; everything else needs models.manage. API keys can’t call it |
| How to sign in with curl | As described in Rules API: before you start |
| Going live | Never applied directly. POST …/promote always creates a change request that someone else holding models.approve approves |
| Scope | Your own workspace only. Nothing is shared between workspaces |
Endpoints at a glance
Section titled “Endpoints at a glance”| Endpoint | What it does | Needs | Answers |
|---|---|---|---|
GET /api/v1/models |
The registry: every version, newest first | models.view |
200 |
GET /api/v1/models/options |
Limits and the standard features | models.view |
200 |
GET /api/v1/models/schedule |
The retraining schedule | models.view |
200 |
PUT /api/v1/models/schedule |
Set the retraining schedule | models.manage |
200 |
GET /api/v1/models/{id} |
One version in full, with its card, metrics, drift and activity | models.view |
200 |
POST /api/v1/models |
Queue a training | models.manage |
202 with the new id |
POST /api/v1/models/{id}/cancel |
Cancel a queued or running training | models.manage |
200 |
DELETE /api/v1/models/{id} |
Delete a version whose training failed or was cancelled | models.manage |
204 |
POST /api/v1/models/{id}/shadow |
Move a trained draft to shadow | models.manage |
204 |
POST /api/v1/models/{id}/unshadow |
Move a shadow model back to draft | models.manage |
204 |
POST /api/v1/models/{id}/promote |
Propose that a shadow model goes live | models.manage |
428, then 202 |
POST /api/v1/models/{id}/retire |
Retire a model | models.manage |
204, or 428 then 202 for the live model when approval applies |
The registry
Section titled “The registry”GET /api/v1/models
Section titled “GET /api/v1/models”Up to 200 versions, newest first.
curl -s -b ~/.sp-cookies "$SP/api/v1/models"[ { "id": "0192f3a1-7c1e-7b52-9a55-2f2f0f6f4d11", "version": 3, "kind": "isolation_forest", "state": "shadow", "train_status": "succeeded", "progress": 1, "window_from": "2026-07-14T00:00:00Z", "window_to": "2026-10-12T00:00:00Z", "row_count": 184220, "sample_rows": 50000, "feature_count": 19, "drift": { "computed_at": "2026-10-14T09:00:02Z", "days": 7, "scored": 15310, "score_psi": 0.03, "features": [{ "key": "customer_sum_24h", "label": "Customer: total value of transactions, last 24 hours", "psi": 0.06 }], "max_psi": 0.06, "level": "stable" }, "created_at": "2026-10-12T08:14:09Z", "finished_at": "2026-10-12T08:16:40Z", "shadow_at": "2026-10-12T08:31:00Z", "trained_by": "Kwesi Appiah" }]| Field | Meaning |
|---|---|
version |
1, 2, 3 … per workspace |
kind |
Always isolation_forest today |
state |
draft (trained or training; scores nothing), shadow (scores recorded, hidden from rules), live (rules read its score), retired |
train_status |
queued, running, succeeded, failed or cancelled |
train_error |
Why the training failed or was refused, in a sentence |
progress |
Training progress, 0 to 1 |
window_from, window_to |
The training window (window_to is exclusive) |
row_count |
Transactions the window held |
sample_rows |
Transactions kept, an even random sample, to grow the trees and measure the baselines |
feature_count |
Inputs the model reads |
drift |
The latest stored drift measurement, only while the version is shadow or live |
created_at, finished_at, shadow_at, live_at, retired_at |
When each step happened |
trained_by, approved_by |
Names. trained_by is absent for a version queued from the command line or by the retraining schedule |
scheduled |
true when the workspace’s retraining schedule asked for this version, not a person |
GET /api/v1/models/options
Section titled “GET /api/v1/models/options”What a training needs, for building a form.
| Field | Meaning |
|---|---|
min_rows |
The fewest transactions a training window may hold on this installation (default 5,000) |
drift_days |
The window drift is measured over (default 7) |
default_window_days, max_window_days |
90 and 180 |
standard_features |
The 16 features every model reads |
GET /api/v1/models/{id}
Section titled “GET /api/v1/models/{id}”Everything in a registry row, plus:
| Field | Meaning |
|---|---|
params |
trees, sample_size, max_rows, and min_rows (the minimum in force when the training was asked for) |
seed |
The same window, features, parameters and seed give the same model, byte for byte |
features |
The model’s inputs, in order: see Features |
metrics |
Absent until trained. score_quantiles (101 values: the minimum, each percentile, the maximum), score_histogram (the share of training scores in each of 20 equal bins over 0 to 1), score_mean, and for each feature its min, median, max and decile baseline |
card |
Absent until trained. The model card |
artifact_format, artifact_sha256, artifact_bytes |
The stored model’s format (sp-iforest/1), SHA-256 fingerprint and size. The stored model itself is never returned |
note |
The note given when training was asked for |
shadow_by, promoted_by, retired_by, retire_reason |
Who moved it to shadow, who proposed going live, who retired it and why |
observed_histogram |
The share of the scores given over the drift window in each of the 20 bins; empty when nothing was scored |
observed_scored |
How many transactions that is |
activity |
Transactions scored per day over the last 30 days: day (YYYY-MM-DD, UTC), state (shadow or live), scored |
drift |
Drift worked out as of now, while the version is shadow or live |
min_rows |
The installation’s current minimum |
404 model not found if there is no such version in your workspace.
Features
Section titled “Features”| Field | Meaning |
|---|---|
key |
Stable identifier. model.reasons in rules lists these |
label |
The feature in plain words, in English. Shown as a reason |
kind |
aggregate (a feature-store aggregate, in spec), variable (a field of the transaction, in variable, such as txn.amount) or ratio (numerator divided by denominator, both feature keys) |
scale |
linear, or log: the model sees sign(x) · ln(1 + |x|), so a few very large amounts don’t hide everything else |
source |
standard (part of every model) or rule (an aggregate one of the workspace’s live rules computes) |
The model card
Section titled “The model card”card is the document for auditors and regulators, written when training finishes and never
changed. Its prose is in English.
| Field | Meaning |
|---|---|
title, version, model_type, generated_at, summary |
What the model is |
data_from, data_to, row_count, sample_rows, features |
What it was trained on |
parameters, seed |
How |
score_median, score_p90, score_p99, score_max |
The spread of its scores on its training sample |
intended_use, not_for, limits |
Lists of sentences |
explanations |
How a score’s reasons are worked out |
artifact_format, artifact_sha256, artifact_contains, artifact_excludes |
What the stored model holds and never holds |
governance |
Shadow before live, a second person to go live, one live model, repeatable training |
| Field | Meaning |
|---|---|
computed_at, days |
When it was measured, and over the last how many days |
scored |
Transactions scored in that window |
score_psi |
Population stability index of the score against training |
features |
The five most shifted features, most shifted first: key, label, psi |
max_psi |
The highest of the score’s and the features’ PSI |
level |
insufficient (fewer than 500 transactions scored: too few to say), stable (under 0.1), moderate (0.1 to 0.25) or significant (above 0.25: train again) |
Training
Section titled “Training”POST /api/v1/models
Section titled “POST /api/v1/models”Queues a training on your workspace’s own transactions. It runs in the background on a worker.
Poll GET /api/v1/models/{id} for train_status and progress.
Every field is optional.
| Field | Default | Meaning |
|---|---|---|
from |
90 days before to |
Start of the training window |
to |
Now | End of the window, exclusive. Not in the future |
trees |
100 |
Trees in the forest, 10 to 500 |
sample_size |
256 |
Transactions each tree is grown on, 32 to 4096 |
max_rows |
50000 |
Transactions kept from the window, 1000 to 500000 |
seed |
Random | 0 to 9007199254740991. Give a version’s seed to train the same model again |
rule_features |
true |
Also read the aggregates the workspace’s live rules compute (up to 16) |
note |
Why this version is being trained, up to 500 characters |
curl -s -b ~/.sp-cookies -X POST "$SP/api/v1/models" \ -H 'Content-Type: application/json' \ -d '{"from": "2026-07-14T00:00:00Z", "to": "2026-10-12T00:00:00Z", "note": "After the merchant product launch"}'{ "id": "0192f3a1-7c1e-7b52-9a55-2f2f0f6f4d11" }The answer is 202. The new version is a draft with train_status: "queued".
| Status | When |
|---|---|
422 |
The window ends before it starts, ends in the future, is longer than 180 days or shorter than 7; a parameter is out of range; or not enough data: the window from … to … holds N transactions, and a model needs at least … to mean anything. Choose a longer window, or train once more transactions have arrived. |
409 |
a training is already queued or running in this workspace; wait for it to finish or cancel it |
A training that is queued can still end as failed, with train_error saying why: too many
transactions to replay, too much feature memory, the time limit, or the worker stopping. The
worker also checks the minimum again on what it actually reads.
POST /api/v1/models/{id}/cancel
Section titled “POST /api/v1/models/{id}/cancel”{ "status": "cancelled" }status is cancelled when the training was still queued, or running when it had started:
the training then stops within a few seconds. 409 this training has already finished otherwise.
DELETE /api/v1/models/{id}
Section titled “DELETE /api/v1/models/{id}”Removes a version whose training failed or was cancelled. 409 for any other: only a
version whose training failed or was cancelled can be deleted; retire a trained one instead. A
trained version is never deleted.
The retraining schedule
Section titled “The retraining schedule”A workspace can have its model trained again weekly or monthly. It is off by default. A scheduled
training is queued and limited exactly like one asked for with POST /api/v1/models, and it
produces a draft only: shadow and going live stay as they are. See
The retraining schedule.
GET /api/v1/models/schedule
Section titled “GET /api/v1/models/schedule”Needs models.view. A workspace that never set a schedule answers
{"frequency": "off", "window_days": 90}.
{ "frequency": "monthly", "window_days": 90, "next_run_at": "2026-11-12T09:30:00Z", "last_run_at": "2026-10-12T09:30:41Z", "last_result": "queued", "last_model_id": "0199e0a1-…", "last_model_version": 4, "updated_at": "2026-09-12T09:30:00Z", "updated_by": "Kwesi Appiah"}| Field | Meaning |
|---|---|
frequency |
off, weekly or monthly |
window_days |
The training window of each scheduled training: this many days up to the moment it is queued. 7 to 180 |
next_run_at |
When the next training is queued. Absent while off |
last_run_at |
When the schedule last came due |
last_result |
queued: a training was queued. skipped: none was, and the next occasion is tried. deferred: another training of the workspace was queued or running, and it is tried again in an hour |
last_detail |
Why the last occasion was skipped or deferred, in a sentence |
last_model_id, last_model_version |
The version the schedule last queued |
updated_at, updated_by |
When the schedule was last set, and by whom |
PUT /api/v1/models/schedule
Section titled “PUT /api/v1/models/schedule”Needs models.manage. Answers 200 with the schedule as GET returns it.
{"frequency": "weekly", "window_days": 60}| Field | Meaning |
|---|---|
frequency |
Required. off, weekly or monthly. Anything else is 422 |
window_days |
7 to 180; 90 when left out. Outside that range is 422 |
- Switching the schedule on, or to another frequency, sets
next_run_atone period from now: seven days forweekly, the same day of the next month formonthly. - Changing only
window_dayskeepsnext_run_at. offclearsnext_run_at.
Audited as model.schedule on entity type risk_model_schedule, with the frequency and window
before and after; a PUT that changes neither writes nothing. Setting the schedule does not go
through approval. Each training the schedule queues is audited as model.train by the system,
with schedule set to the frequency.
Shadow
Section titled “Shadow”POST /api/v1/models/{id}/shadow
Section titled “POST /api/v1/models/{id}/shadow”Moves a trained draft to shadow. The model starts scoring every transaction; its scores are
counted for drift and the Models page and never reach a rule. 204.
409 when the version has no trained model, isn’t a draft, or another version is already in
shadow: a workspace runs one shadow model at a time. Move it back to draft or retire it first.
POST /api/v1/models/{id}/unshadow
Section titled “POST /api/v1/models/{id}/unshadow”Moves a shadow model back to draft. It stops scoring. 204; 409 if it isn’t in shadow.
Going live
Section titled “Going live”POST /api/v1/models/{id}/promote
Section titled “POST /api/v1/models/{id}/promote”Proposes that a shadow model becomes the workspace’s live model. This is always a change
request, whatever the workspace’s approval policy.
- Call it without a reason. The answer is
428 Reason required. Nothing is stored. (If the version isn’t in shadow you get409here instead: a model goes live from shadow, after its scores have been looked at.) - Call it again with the
X-Change-Reasonheader, 3 to 2,000 characters. The answer is202with the change request.
curl -s -b ~/.sp-cookies -X POST "$SP/api/v1/models/$ID/promote" \ -H 'X-Change-Reason: Two weeks in shadow, drift stable, score spread matches training. MR-212'Someone else holding models.approve then approves or rejects it through the
Change requests API (action type models.lifecycle, operation
model.promote). On approval:
- the version becomes
live, withpromoted_bythe proposer andapproved_bythe approver; - the version that was live is
retired, withretire_reasonReplaced by version N.
The database refuses a live model with no approver, or whose approver is its proposer. If the version left shadow, or the live version changed, while the request waited, the request is closed as stale and nothing is applied.
Retiring
Section titled “Retiring”POST /api/v1/models/{id}/retire
Section titled “POST /api/v1/models/{id}/retire”| Field | Meaning |
|---|---|
reason |
Required, 3 to 500 characters. Kept in the registry and the audit log |
A draft (trained) or shadow version is retired straight away: 204.
The live model is retired straight away too, unless the workspace requires approval for
Risk models (models.lifecycle, on by default once two people hold models.approve). Then
it is a change request: 428 without X-Change-Reason, 202 with it. Once the live model is
retired, rules read model.available == false and a score of 0 until another version goes
live.
409 if the version is already retired, or was never trained (delete it instead). 422 if the
reason is missing or too long.
Where a score appears in other APIs
Section titled “Where a score appears in other APIs”A score is stored only for the live model, and only with its reasons.
| Where | Field |
|---|---|
GET /api/v1/decisions/{id} |
model: the live model’s score when the decision was made |
| An alert’s hits | model on a hit: the score of the transaction that hit |
"model": { "model_id": "0192f3a1-7c1e-7b52-9a55-2f2f0f6f4d11", "version": 3, "score": 0.71, "percentile": 99.2, "reasons": [ { "feature": "amount_vs_avg_30d", "label": "Amount compared with the customer's 30-day average", "value": 14.2, "contribution": 0.31 }, { "feature": "customer_count_24h", "label": "Customer: number of transactions, last 24 hours", "value": 23, "contribution": 0.12 } ]}contribution is a feature’s share of the score: over all the model’s features the shares add
up to log2(score) + 1, and a positive one pushed the score up. Up to three reasons are kept.
The answer to POST /api/v1/decisions does not carry the score: your payment system gets
the outcome and the rules that fired, as before.
What is audited
Section titled “What is audited”model.train, model.train_cancel, model.delete, model.shadow, model.unshadow,
model.promote and model.retire, on entity type risk_model, and model.schedule on entity type
risk_model_schedule. Reads are not audited. See the
audit log.