Custom models
Create your own architecture, browse your models and the public catalog, and fetch the workspace recommendation.
Create a model
POST /v1/models creates a model in your workspace. Every kind shares
the same catalog fields plus a kind-specific body; this page shows the
custom body (for the foundation body see
Foundation models).
curl -X POST "$API_BASE/v1/models" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{
"kind": "custom",
"name": "LSTM long range for markets",
"purpose": "Improve the prediction of the next value in a timeseries dataset",
"description": "Stacked LSTM layers for enhanced time series forecasting",
"visibility": "private",
"categories": ["stocks", "prediction"],
"tags": ["stock market"],
"metadata": {
"framework": "TensorFlow",
"framework_version": "2.x",
"training_environment": "cloud"
},
"model_blueprint": {
"model_type": "sequential",
"layers": [
{ "type": "Input", "units": 3 },
{ "type": "LSTM", "units": 64, "activation": "tanh", "return_sequences": false },
{ "type": "Dropout", "rate": 0.2 },
{ "type": "Dense", "units": 1, "activation": "linear" }
]
},
"compilation_details": {
"loss": "huber",
"optimizer": { "type": "adamw", "learning_rate": 0.001, "weight_decay": 0.01 },
"metrics": ["mae", "rmse"]
},
"training_details": { "epochs": 200, "batch_size": 64, "sequence_length": 20 },
"data_split": { "test_split": 0.2, "validation_split": 0.2 },
"data_preprocessing": { "global_scaler": "MinMaxScaler" }
}'from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.models.post_models(json={
"kind": "custom",
"name": "LSTM long range for markets",
"purpose": "Improve the prediction of the next value in a timeseries dataset",
"description": "Stacked LSTM layers for enhanced time series forecasting",
"visibility": "private",
"categories": ["stocks", "prediction"],
"tags": ["stock market"],
"metadata": {
"framework": "TensorFlow",
"framework_version": "2.x",
"training_environment": "cloud",
},
"model_blueprint": {
"model_type": "sequential",
"layers": [
{"type": "Input", "units": 3},
{
"type": "LSTM",
"units": 64,
"activation": "tanh",
"return_sequences": False,
},
{"type": "Dropout", "rate": 0.2},
{"type": "Dense", "units": 1, "activation": "linear"},
],
},
"compilation_details": {
"loss": "huber",
"optimizer": {
"type": "adamw",
"learning_rate": 0.001,
"weight_decay": 0.01,
},
"metrics": ["mae", "rmse"],
},
"training_details": {"epochs": 200, "batch_size": 64, "sequence_length": 20},
"data_split": {"test_split": 0.2, "validation_split": 0.2},
"data_preprocessing": {"global_scaler": "MinMaxScaler"},
})
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.models.postModels({
json: {
kind: "custom",
name: "LSTM long range for markets",
purpose: "Improve the prediction of the next value in a timeseries dataset",
description: "Stacked LSTM layers for enhanced time series forecasting",
visibility: "private",
categories: ["stocks", "prediction"],
tags: ["stock market"],
metadata: {
framework: "TensorFlow",
framework_version: "2.x",
training_environment: "cloud",
},
model_blueprint: {
model_type: "sequential",
layers: [
{ type: "Input", units: 3 },
{
type: "LSTM",
units: 64,
activation: "tanh",
return_sequences: false,
},
{ type: "Dropout", rate: 0.2 },
{ type: "Dense", units: 1, activation: "linear" },
],
},
compilation_details: {
loss: "huber",
optimizer: {
type: "adamw",
learning_rate: 0.001,
weight_decay: 0.01,
},
metrics: ["mae", "rmse"],
},
training_details: { epochs: 200, batch_size: 64, sequence_length: 20 },
data_split: { test_split: 0.2, validation_split: 0.2 },
data_preprocessing: { global_scaler: "MinMaxScaler" },
},
});
{
"data": {
"uid": "c1c4d85a-42d9-476a-a6f1-fa8b22e29671",
"message": "Model created successfully"
}
}Request body — catalog fields (every kind)
| Field | Meaning |
|---|---|
name required | Display name for the catalog. |
purpose required | One sentence on what the model is for. |
description required | Longer free-text description. |
visibility required | private (only you) or public (listed in the public catalog). |
categories required | Array of category strings for catalog filtering. |
tags required | Array of tag strings for catalog filtering. |
metadata required | Catalog metadata: framework, framework_version, training_environment. |
kind optional | custom (default) or foundation. BYOM models are created through their own flow. |
Request body — custom architecture
| Field | Meaning |
|---|---|
model_blueprint required | The architecture. Neural frameworks: { "model_type": "sequential", "layers": […] }. scikit-learn: { "estimator": …, "hyperparameters": … }. XGBoost: { "hyperparameters": … }. |
compilation_details required | Loss, optimizer, and metrics for neural frameworks — { "loss", "optimizer": { "type", "learning_rate", … }, "metrics": […] }. Send {} for sklearn and XGBoost. |
training_details optional | Training knobs: epochs, batch_size, sequence_length (the lookback window per sample). |
data_split optional | test_split and validation_split fractions. |
data_preprocessing optional | e.g. { "global_scaler": "MinMaxScaler" }. |
Supported frameworks: tensorflow (aliases keras, tf), pytorch,
sklearn, and xgboost. TensorFlow and PyTorch use the layer schema
above; scikit-learn takes model_blueprint.estimator +
hyperparameters, XGBoost takes model_blueprint.hyperparameters — both
with an empty compilation_details.
Before saving, the platform actually builds your model, runs a
prediction, compiles it, and executes one training step on synthetic
data. Anything that would crash a real run — incompatible layers, an
unknown loss, a metric that doesn't fit the output shape — comes back as
a 400 with a specific fix, not as a failed training an hour later. One
fix is applied automatically: a recurrent layer with
return_sequences: true followed directly by Dense is corrected for
you.
Errors
| Status | Why | Example message |
|---|---|---|
400 | A required catalog field is missing | "Missing required fields: purpose, categories" |
400 | Bad visibility or kind value | "Visibility must be either 'public' or 'private'" |
400 | The kind-specific body failed validation | "Invalid custom model: Neural-network models must contain a non-empty 'layers' array under model_blueprint." |
403 | Plan quota reached | "Model limit exceeded. Your plan allows 10 models per workspace." |
500 | The model couldn't be saved | "Failed to create model" |
Once created, train the model by referencing its uid from a
pipeline.
List your models
Returns the models you own in this workspace, newest first:
curl "$API_BASE/v1/models" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID"from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.models.get_models()
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.models.getModels();
{
"data": {
"models": [
{
"uid": "c1c4d85a-42d9-476a-a6f1-fa8b22e29671",
"kind": "custom",
"name": "LSTM long range for markets",
"visibility": "private",
"categories": ["stocks", "prediction"],
"tags": ["stock market"],
"rating": 0,
"in_use_by": 0,
"created_at": "2026-07-15T08:30:00+00:00",
…
}
]
}
}Get one model
curl "$API_BASE/v1/models/$MODEL_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.models.get_models_by_blueprint_uid(MODEL_ID)
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.models.getModelsByBlueprintUid(MODEL_ID);
{
"data": {
"model": {
"uid": "c1c4d85a-42d9-476a-a6f1-fa8b22e29671",
"kind": "custom",
"name": "LSTM long range for markets",
"owner_name": "Ada",
"owner_username": "ada",
…
}
}
}You can fetch any model you own and any public model; owner details are
attached for catalog display. Foundation-model ids (pfm__…,
extfm__…) work here too and return the synthesized catalog row.
| Status | Why | Example message |
|---|---|---|
403 | The model is private and isn't yours | "Unauthorized access to model" |
404 | No model with that id | "Model not found" |
Browse the public catalog
One paginated list across all kinds, plus foundation models:
curl "$API_BASE/v1/models/public?kind=all&page=1&limit=10&sort_by=rating&sort_order=desc" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID"from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.models.get_models_public(params={
"kind": "all",
"page": 1,
"limit": 10,
"sort_by": "rating",
"sort_order": "desc",
})
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.models.getModelsPublic({
params: {
kind: "all",
page: 1,
limit: 10,
sort_by: "rating",
sort_order: "desc",
},
});
{
"data": {
"models": [
{
"uid": "pfm__predictfm-f-v0.1",
"kind": "foundation_backbone",
"name": "PredictFM · predictfm-f-v0.1",
"visibility": "public",
"featured": 1,
"foundation": { "task": "forecast", "license": "Apache-2.0", … },
…
},
{
"uid": "8d1f0a3e-…",
"kind": "custom",
"name": "LSTM long range for markets",
"owner_details": { "uid": "949e4f25-…", "username": "ada", … },
…
}
],
"pagination": {
"total": 42,
"page": 1,
"page_count": 5,
"has_next": true,
"has_prev": false
}
}
}Query parameters
| Parameter | Meaning |
|---|---|
kind optional | all (default), custom, byom, foundation (fine-tune models plus foundation models), or foundation_backbone (foundation models only). |
page optional | Page number, ≥ 1. Defaults to 1. |
limit optional | Rows per page, ≤ 100. Defaults to 10. |
search optional | Matches the model name, case-insensitive. |
framework / training_environment / owner optional | Exact-match filters on catalog metadata. |
categories / tags optional | Repeatable, e.g. categories=weather&categories=temperature. |
sort_by optional | name, created_at, rating, or in_use_by. Default sort: featured, then rating, then usage. |
sort_order optional | asc or desc. |
include_deprecated optional | Include deprecated foundation models. Defaults to false. |
| Status | Why | Example message |
|---|---|---|
400 | page or limit isn't an integer | "Invalid pagination parameters" |
Get the recommended model
When signal analysis finds a model architecture that fits your workspace's data, it stores a single workspace-level recommendation. Any workspace member can fetch it:
curl "$API_BASE/v1/models/recommended" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID"from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.models.get_models_recommended()
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.models.getModelsRecommended();
{
"data": {
"model": {
"uid": "5b7e2c19-…",
"kind": "custom",
"name": "Recommended: GRU for daily_sales",
"recommended": true,
…
}
}
}Returns {"data": {"model": null}} when no recommendation exists yet.

