Create & manage
Create, list, inspect, edit, and delete pipelines.
Create a pipeline
POST /v1/pipelines binds one or more models to a segment with a
schedule and a promotion policy. A 201 means the pipeline exists — and
if you didn't set a recurring schedule, one training run is queued
immediately.
curl -X POST "$API_BASE/v1/pipelines" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{
"name": "Daily sales forecaster",
"segment": "SEGMENT_ID",
"workspace_id": "'$WORKSPACE_ID'",
"models": [
{ "model": "MY_CUSTOM_MODEL_ID" },
{ "family": "predictfm", "slug": "predictfm-f-v0.1" }
],
"forecast_horizon": 14,
"training_schedule": {
"start_time": "02:00:00",
"end_time": "05:00:00",
"days": ["monday", "thursday"],
"frequency": "86400",
"timezone": "UTC"
},
"promotion_policy": { "mode": "auto" }
}'from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.pipelines.post_pipelines(json={
"name": "Daily sales forecaster",
"segment": "SEGMENT_ID",
"workspace_id": WORKSPACE_ID,
"models": [
{"model": "MY_CUSTOM_MODEL_ID"},
{"family": "predictfm", "slug": "predictfm-f-v0.1"},
],
"forecast_horizon": 14,
"training_schedule": {
"start_time": "02:00:00",
"end_time": "05:00:00",
"days": ["monday", "thursday"],
"frequency": "86400",
"timezone": "UTC",
},
"promotion_policy": {"mode": "auto"},
})
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.pipelines.postPipelines({
json: {
name: "Daily sales forecaster",
segment: "SEGMENT_ID",
workspace_id: WORKSPACE_ID,
models: [
{ model: "MY_CUSTOM_MODEL_ID" },
{ family: "predictfm", slug: "predictfm-f-v0.1" },
],
forecast_horizon: 14,
training_schedule: {
start_time: "02:00:00",
end_time: "05:00:00",
days: ["monday", "thursday"],
frequency: "86400",
timezone: "UTC",
},
promotion_policy: { mode: "auto" },
},
});
{
"data": {
"uid": "9f2c41d8-…",
"message": "Pipeline created successfully"
}
}Request body
| Field | Meaning |
|---|---|
name required | Display name for the pipeline. |
segment required | The segment this pipeline trains on. |
workspace_id required | Must match your X-Workspace-Id header — a mismatch is rejected with 400. |
models required | The model pool — an array of entries, one per model that trains each run. See the entry shape below. |
forecast_horizon optional | How many steps ahead every model predicts. Pipeline-level and shared by all entries so cycle scores stay comparable. Defaults to 5. |
training_schedule optional | When recurring runs fire. See the schedule shape below. Defaults to run once: no recurring runs, one immediate training at creation. |
promotion_policy optional | What happens when a run finishes: { "mode": "manual" | "auto" | "off", "demote_previous": true }. Defaults to manual (winner waits for your decision); auto deploys the winner when it beats the serving version; off never proposes. demote_previous (default true) retires the previous deployment when a new one goes live. |
retrain_triggers optional | Retrain when fresh data lands: { "on_new_data": { "enabled": false, "min_rows": …, "quiet_seconds": …, "max_wait_seconds": …, "cooldown_seconds": … } }. Disabled unless you turn it on. |
budget optional | Spending guardrail: { "monthly_credit_cap": 500 }. When a month's training spend reaches the cap, scheduled runs stop until the month rolls over. null or 0 means no cap. |
active optional | Set false to create the pipeline paused. Defaults to true. |
The models array
Each entry references one model by its catalog ID — a custom model, a BYOM upload, or a foundation model, in any mix. The entries form a pool of equals: every run trains all of them, and the best score becomes the promotion candidate (see The model pool). You never say what kind a model is — the platform resolves that from the ID.
| Field | Meaning |
|---|---|
model required | A catalog model ID — from your models or the foundation catalog. Omit it only when using the family + slug shorthand. |
family optional | Foundation shorthand: the model family (e.g. predictfm, chronos_bolt). Use together with slug instead of model. |
slug optional | Foundation shorthand: the model version within the family. |
lookback optional | Foundation entries only. How much history the model consumes per sample. Defaults to 2048. |
intensity optional | Foundation entries only. Fine-tuning effort: light, adapt (default), or deep. Deeper costs more and adapts more. |
There's no cap on the number of entries — but every entry is one full paid training per run, governed by your credit balance, the pipeline's monthly budget, and your plan's training quotas.
One rule: a BYOM model must be the only entry. BYOM arrives pre-trained and can't race in a training cycle, so a BYOM pipeline references exactly one model and goes straight to deployment.
The training_schedule object
| Field | Meaning |
|---|---|
days optional | Weekdays the schedule fires, lowercase: ["monday", "thursday"]. An empty array means "run once" — no recurring runs; one training is queued immediately at creation. |
start_time optional | Start of the daily window, HH:MM:SS. Defaults to 00:00:00. |
end_time optional | End of the daily window, HH:MM:SS. Defaults to 23:59:59. |
frequency optional | Seconds between runs inside the window, as a string. "86400" (the default) = once per day. |
timezone optional | IANA timezone for the window, e.g. "UTC". |
Validation before anything trains
Every entry is contract-checked at create time against the shared
segment and forecast_horizon: data-sufficiency window math, label
arity, and each foundation family's horizon cap. A model that can't
train on this segment is rejected with a concrete message — nothing is
charged.
Errors
| Status | Why | Example message |
|---|---|---|
400 | Missing field, workspace mismatch, a bad models entry, or the segment is too small for the requested horizon | "Missing required field: segment" |
400 | A BYOM model listed with other entries | "BYOM models arrive pre-trained and can't race in a training cycle — a BYOM pipeline must reference exactly one model" |
403 | No workspace access, no access to a referenced model, or plan quota reached | "Pipeline limit exceeded. Your plan allows 5 pipelines per workspace." |
404 | A referenced model or the segment doesn't exist | "Segment with ID … not found in this workspace" |
List pipelines
GET /v1/pipelines returns every pipeline in the workspace, newest
first.
curl "$API_BASE/v1/pipelines?page=0&per_page=25" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID"from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.pipelines.get_pipelines(params={"page": 0, "per_page": 25})
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.pipelines.getPipelines({ params: { page: 0, per_page: 25 } });
{
"data": {
"pipelines": [
{
"uid": "9f2c41d8-…",
"name": "Daily sales forecaster",
"segment_id": "SEGMENT_ID",
"models": [
{ "model": "MY_CUSTOM_MODEL_ID", "kind": "custom" },
{ "model": "pfm__predictfm-f-v0.1", "kind": "foundation",
"family": "predictfm", "slug": "predictfm-f-v0.1" }
],
"active": true,
"in_training": false,
"accuracy_score": 0.94,
"top_model": { "uid": "TRAINING_ID", "score": 0.94 },
"latest_training_status": "completed",
"promotion_policy": { "mode": "auto", "demote_previous": true },
"has_pending_promotion": false,
"schedule": { "days": ["monday", "thursday"], "frequency": "86400" },
"created_at": "2026-07-15T08:30:00+00:00"
}
],
"total_count": 1,
"page": 0,
"per_page": 25,
"total_pages": 1
}
}Query parameters
| Parameter | Meaning |
|---|---|
page optional | Zero-based page number. Defaults to the first page. |
per_page optional | Rows per page. Defaults to 100. |
sort_column optional | Field to sort by. Defaults to created_at. |
sort_direction optional | asc or desc (default). |
Response fields
| Field | Meaning |
|---|---|
models | The pool, with each entry's resolved kind (custom, byom, foundation) and — for foundation entries — family + slug. |
active | Whether scheduled and triggered runs fire. Paused pipelines also refuse manual training. |
in_training | true while a run is in flight. |
accuracy_score / top_model | The best score so far, and which training produced it. |
latest_training_status | Status of the most recent run: queued, running, completed, failed. |
has_pending_promotion | true when a winner is waiting for your promotion decision (manual mode). |
schedule | The recurring schedule, echoed back. |
Inspect one pipeline
GET /v1/pipelines/{id} returns the full pipeline document — the same
fields as a list row, plus the automation state (budget,
budget_state, retrain_triggers) and aggregate training cost. For
the run-by-run history, use Trainings.
curl "$API_BASE/v1/pipelines/$PIPELINE_ID" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID"from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.pipelines.get_pipelines_by_model_id(PIPELINE_ID)
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.pipelines.getPipelinesByModelId(PIPELINE_ID);
Errors
| Status | Why | Example message |
|---|---|---|
404 | The pipeline doesn't exist in this workspace, or you can't access it | "Pipeline with ID … not found" |
Edit a pipeline
PUT /v1/pipelines/{id} — send only the fields you're changing.
curl -X PUT "$API_BASE/v1/pipelines/$PIPELINE_ID" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{ "active": false }'from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.pipelines.put_pipelines_by_model_id(PIPELINE_ID, json={"active": False})
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.pipelines.putPipelinesByModelId(PIPELINE_ID, { json: { active: false } });
{ "data": { "message": "Pipeline updated successfully" } }Request body
All fields optional — anything omitted keeps its current value.
| Field | Meaning |
|---|---|
name optional | Rename the pipeline. |
active optional | false pauses the pipeline: scheduled runs stop firing and train refuses until re-enabled. |
models optional | Replace the model pool — same shape as create. Re-validated against the segment and horizon before saving. |
segment optional | Move the pipeline to a different segment. Every pool entry is re-validated against it. |
forecast_horizon optional | Change the prediction horizon. Re-validated against segment size and each foundation entry's cap. |
training_schedule optional | Replace the recurring schedule — same shape as create. |
promotion_policy optional | Change the automation mode or demote_previous. |
retrain_triggers optional | Enable/disable or retune the new-data trigger. |
budget optional | Change or clear the monthly credit cap. |
BYOM pipelines are bound to their uploaded model at creation — the
models list can't be changed afterwards, and a BYOM model can't be
swapped into an existing pipeline. Create a new pipeline instead.
Errors
| Status | Why | Example message |
|---|---|---|
400 | A bad models entry, the post-edit contract fails, or a BYOM swap was attempted | "Model '…': segment has too few rows for the requested horizon" |
404 | The pipeline or the new segment doesn't exist in this workspace | "Pipeline with ID … not found or you don't have permission to edit it" |
Delete a pipeline
DELETE /v1/pipelines/{id} removes the pipeline and its schedule.
curl -X DELETE "$API_BASE/v1/pipelines/$PIPELINE_ID" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID"from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.pipelines.delete_pipelines_by_model_id(PIPELINE_ID)
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.pipelines.deletePipelinesByModelId(PIPELINE_ID);
{ "data": { "message": "Pipeline deleted successfully" } }Deleting a pipeline doesn't tear down its live deployment — unpromote first if one is serving.
Errors
| Status | Why | Example message |
|---|---|---|
400 | The pipeline is mid-training — wait for the run to finish | "Cannot delete a pipeline while it is training" |
404 | The pipeline doesn't exist in this workspace | "Pipeline with ID … not found" |

