Tournament & champion
How candidate models compete, how to read the leaderboard, and how to pick or cancel a champion.
After discovery, the goal fits a model. Instead of betting on one architecture, the platform runs a tournament: a lineup of candidate models — classical learners, foundation models, and any of your own models you selected — all train on the same discovered drivers and are scored on the same purged walk-forward folds, in the goal type's own metric. The best scorer becomes the champion and trains on full history for serving.
Tournaments start automatically after a discovery run when auto-fit is on,
or on demand with POST /v1/goals/{goal_id}/train.
Read the leaderboard
curl "$API_BASE/v1/goals/$GOAL_ID/tournament" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID"from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.goals.get_goals_by_goal_id_tournament(GOAL_ID)
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.goals.getGoalsByGoalIdTournament(GOAL_ID);
{
"data": {
"tournament": {
"tournament_id": "e7a1…",
"goal_id": "b7e9c2d4-…",
"status": "completed",
"metric": { "name": "smape", "direction": "lower" },
"n_folds": 5,
"entries": [
{
"preset_id": "gbt_belief",
"label": "HistGradientBoosting",
"display_name": "Driver-weighted trees",
"engine": "classical",
"status": "completed",
"score": 8.41,
"rank": 1,
"fold_scores": [9.1, 8.3, 8.2, 8.5, 8.0],
"duration_s": 74.2,
"training_id": "t_5b2e…",
"model_id": "9f2c41d8-…",
"error": null
},
{
"preset_id": "seasonal_naive",
"display_name": "Steady baseline",
"status": "completed",
"score": 12.90,
"rank": 2,
"…": "…"
}
],
"excluded": [
{ "preset_id": "trend_classifier", "reason": "Applies to trend goals only" }
],
"champion": {
"preset_id": "gbt_belief",
"model_id": "9f2c41d8-…",
"training_id": "t_5b2e…",
"score": 8.41,
"training_status": "completed",
"executed": true,
"accuracy_score": 91.4,
"progress": null,
"deployment": {
"promotion_id": "p_88c1…",
"status": "deployed",
"ready": true
}
},
"auto_deploy": "review",
"created_at": "2026-07-15T09:40:00+00:00",
"finished_at": "2026-07-15T09:52:31+00:00"
}
}
}Response fields
| Field | Meaning |
|---|---|
entries | One row per candidate, with per-fold scores, the final score, and rank. Statuses move pending → running → completed (or failed / cancelled); failed entries carry an error and are skipped, so one bad candidate never blocks the board. |
excluded | Candidates that didn't make the lineup, each with a human-readable reason. |
champion | The winner, enriched with its live serving-training state (training_status, progress) and its deployment state — the full arc from "won the board" to "serving". |
auto_deploy | The goal's deploy policy: off, review, or auto. On auto, the champion ships itself; on review, it waits for you. |
metric / n_folds | The scoring metric (with its direction) and how many purged walk-forward folds every candidate was scored on. |
Returns 404 ("Goal not found") for unknown goals; tournament is
null if none has run yet.
Select a champion manually
Deploy any scored entry instead of the automatic best — for example, a simpler model whose score is close enough:
curl -X POST "$API_BASE/v1/goals/$GOAL_ID/tournament/champion" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{ "preset_id": "seasonal_naive" }'from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.goals.post_goals_by_goal_id_tournament_champion(GOAL_ID, json={"preset_id": "seasonal_naive"})
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.goals.postGoalsByGoalIdTournamentChampion(GOAL_ID, { json: { preset_id: "seasonal_naive" } });
{
"data": {
"tournament_id": "e7a1…",
"preset_id": "seasonal_naive",
"model_id": "3ac9…",
"training_id": "t_91d0…",
"status": "training",
"unchanged": false
}
}| Field | Meaning |
|---|---|
preset_id required | The scored entry to make champion — from the leaderboard's entries. |
The chosen entry materializes through the same path as an automatic
champion — full-history training, then the standard promotion gate. Picking
the entry that's already champion returns unchanged: true.
| Status | Why | Example message |
|---|---|---|
400 | Missing body field | "preset_id is required" |
404 | No goal, or no tournament yet | "No tournament for this goal" |
409 | The entry can't be selected (not scored, or failed) | "Could not select that model" |
Cancel
Three scopes, all cooperative — workers stop at their next checkpoint:
The whole tournament. Aborts fitting; no champion is selected.
curl -X POST "$API_BASE/v1/goals/$GOAL_ID/tournament/cancel" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID"from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.goals.post_goals_by_goal_id_tournament_cancel(GOAL_ID)
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.goals.postGoalsByGoalIdTournamentCancel(GOAL_ID);
{ "data": { "tournament_id": "e7a1…", "status": "cancelling", "cancelled": true } }If the tournament already finished, you get 200 with cancelled: false
and "Tournament is not running." — nothing to stop.
One entry. Drop a single stuck candidate; the rest keep fitting and a champion is still selected from whatever scored:
curl -X POST "$API_BASE/v1/goals/$GOAL_ID/tournament/entries/fm_adapter/cancel" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID"from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.goals.post_goals_by_goal_id_tournament_entries_by_preset_id_cancel(GOAL_ID, "fm_adapter")
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.goals.postGoalsByGoalIdTournamentEntriesByPresetIdCancel(GOAL_ID, "fm_adapter");
{ "data": { "tournament_id": "e7a1…", "preset_id": "fm_adapter", "status": "cancelling" } }status is cancelled if the entry was still pending, cancelling if it
was mid-flight. Returns 409 if the tournament isn't running or the entry
already finished.
The champion's serving training. The full-history training that runs after the board completes can also be stopped:
curl -X POST "$API_BASE/v1/goals/$GOAL_ID/tournament/training/cancel" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID"from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.goals.post_goals_by_goal_id_tournament_training_cancel(GOAL_ID)
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.goals.postGoalsByGoalIdTournamentTrainingCancel(GOAL_ID);
{
"data": {
"training_id": "t_5b2e…",
"pipeline_id": "9f2c41d8-…",
"status": "cancelling",
"cancelled": true
}
}Returns 404 ("No champion training to cancel") when there's nothing in
flight, or 200 with "Training already finished." when it's done.
The model lineup
See the candidate catalog — every model the tournament can field — before
or after creating a goal. Pass goal_type to flag which apply to that
intent:
curl "$API_BASE/v1/goals/model-lineup?goal_type=forecast_value" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID"from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.goals.get_goals_model_lineup(params={"goal_type": "forecast_value"})
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.goals.getGoalsModelLineup({ params: { goal_type: "forecast_value" } });
{
"data": {
"models": [
{
"id": "gbt_belief",
"label": "HistGradientBoosting",
"display_name": "Driver-weighted trees",
"engine": "Classical",
"description": "Gradient-boosted trees weighted by discovered driver beliefs.",
"always_on": false,
"relevant": true,
"cost_weight": 1.0,
"cost_tier": "Standard",
"metered": false
}
]
}
}Use these id values in the goal's options.model_selection
(include / exclude) to shape the lineup — the tournament charge scales
with it, so a lighter selection costs proportionally less. metered models
additionally charge their standard training meters while fitting.

