Base URL (local dev):
http://127.0.0.1:7878Server: Rust / axum 0.7,cargo run --bin studio --releaseAuth: None yet (Phase 3 adds FastAPI JWT gateway in front of this service) Content-Type: All request bodies areapplication/jsonunless noted asmultipart/form-dataAll responses are JSON unless noted as file downloads or SSE streams
Every endpoint returns JSON. Errors have the shape:
{ "error": "human-readable message" }Successful mutations return at minimum:
{ "ok": true }The tab_unlock field present on several GET responses is a bitmask (u8) used by the frontend to know which workflow tabs are currently accessible:
- bit 0 (0x01) — Architect (always unlocked)
- bit 1 (0x02) — Dataset (unlocked when a spec is saved)
- bit 2 (0x04) — Train (unlocked when a dataset is loaded)
- bit 3 (0x08) — Evaluate (unlocked when training is Done or Stopped)
- bit 4 (0x10) — Test (always unlocked)
Returns the current network architecture spec, hyperparameters, and any pending flash message.
Response:
{
"spec": {
"name": "my_model",
"description": "MNIST classifier",
"input_size": 784,
"loss_type": "cross_entropy",
"layers": [
{ "neurons": 256, "activation": "relu" },
{ "neurons": 128, "activation": "relu" },
{ "neurons": 10, "activation": "softmax" }
],
"metadata": {
"input_type": { "type": "ImageGrayscale", "width": 28, "height": 28 },
"output_labels": ["0","1","2","3","4","5","6","7","8","9"],
"description": "..."
}
},
"hyperparams": {
"learning_rate": 0.01,
"batch_size": 32,
"epochs": 50
},
"tab_unlock": 3,
"flash": null
}spec and hyperparams are null when no architecture has been saved yet.
flash is { "kind": "success" | "error", "text": "..." } or null.
Saves a new or updated network architecture. Also accepts an optional input_type field to bake model metadata (e.g. ImageGrayscale) into the model from design time, not just training time.
Request body:
{
"name": "my_model",
"description": "optional description",
"input_size": 784,
"loss_type": "cross_entropy",
"learning_rate": 0.01,
"batch_size": 32,
"epochs": 50,
"layers": [
{ "neurons": 256, "activation": "relu" },
{ "neurons": 128, "activation": "relu" },
{ "neurons": 10, "activation": "softmax" }
],
"input_type": {
"kind": "grayscale",
"width": 28,
"height": 28
}
}Valid loss_type values: "mse" | "cross_entropy" | "bce" | "mae" | "huber"
Valid activation values: "sigmoid" | "relu" | "identity" | "softmax" | "tanh" | "leaky_relu" | "elu" | "gelu" | "swish"
Valid input_type.kind values: "numeric" | "grayscale" | "rgb" (width + height required for image kinds)
Validation rules:
- A
softmaxoutput layer requiresloss_type = "cross_entropy" - A
sigmoidoutput layer suggestsloss_type = "bce" input_sizemust match the input dimension of the first layer
Response: { "ok": true } or { "error": "..." }
Returns the currently loaded dataset state.
Response:
{
"loaded": true,
"source_name": "MNIST IDX",
"feature_count": 784,
"label_count": 10,
"total_rows": 60000,
"train_rows": 54000,
"val_rows": 6000,
"val_split_pct": 10,
"preview_rows": [
{ "inputs": [0.0, 0.123, ...], "labels": [0.0, 0.0, 1.0, ...] }
],
"tab_unlock": 7,
"error": null
}When no dataset is loaded, loaded is false and all other fields are null.
Upload a CSV file as a dataset. The first row is treated as a header (ignored). All columns are numeric floats. The last N columns are labels; the rest are features.
Request: multipart/form-data
- Field
dataset(file): CSV file - Field
val_split(text, optional): validation split percentage, 0–50 (default: 10) - Field
label_cols(text, optional): number of label columns from the right (default: 1)
Response: DatasetResponse (same shape as GET /api/dataset) with error set on failure.
Upload MNIST-format IDX binary files (images + labels).
Request: multipart/form-data
- Field
images(file): IDX3 image file (e.g.train-images-idx3-ubyte) - Field
labels(file): IDX1 label file (e.g.train-labels-idx1-ubyte) - Field
val_split(text, optional): validation split percentage (default: 10)
Pixel values are normalized from [0, 255] to [0.0, 1.0]. Labels are one-hot encoded.
Response: DatasetResponse with error set on failure.
Load a built-in toy dataset.
Request body:
{ "name": "xor", "val_split": 20 }Valid name values: "xor" | "circles" | "blobs"
Response: DatasetResponse with error set on failure.
Returns current training status and configuration summary.
Response:
{
"status": "idle",
"total_epochs": null,
"model_path": null,
"elapsed_total_ms": null,
"was_stopped": null,
"fail_reason": null,
"epoch_history": [],
"spec_name": null,
"tab_unlock": 7
}status values:
"idle"— no training started"running"— training in progress;total_epochsis set"done"— training complete;model_path,elapsed_total_ms,was_stoppedare set"failed"— training failed;fail_reasonis set
epoch_history is an array of EpochStats objects (see SSE section for shape).
Starts a training run in a background thread. Requires an architecture spec and loaded dataset (otherwise returns an error).
Request: No body required.
Response: { "ok": true } or { "error": "Set up architecture and dataset first." }
Once started, connect to GET /api/train/events to receive real-time progress.
Signals the running training job to stop after the current batch. The model is saved with whatever epochs have completed.
Request: No body required.
Response: { "ok": true }
Server-Sent Events stream for real-time training progress. Connect with EventSource. The stream stays open until training ends.
Event types:
event: epoch
data: {"epoch":1,"total_epochs":50,"train_loss":0.3241,"val_loss":0.3102,"train_accuracy":0.91,"val_accuracy":0.92,"elapsed_ms":843}
event: done
data: {"model_path":"trained_models/my_model.json","elapsed_total_ms":42000,"epochs_completed":50}
event: stopped
data: {"model_path":"trained_models/my_model.json","elapsed_total_ms":8000,"epoch_reached":10,"total_epochs":50}
event: failed
data: {"reason":"Layer size mismatch: ..."}
Reconnecting clients receive replayed epoch events from history before live events resume.
Keep-alive pings are sent as SSE comments every 15 seconds: : ping
EpochStats shape:
{
"epoch": 1,
"total_epochs": 50,
"train_loss": 0.3241,
"val_loss": 0.3102,
"train_accuracy": 0.9123,
"val_accuracy": 0.9201,
"elapsed_ms": 843
}Returns the full epoch history and aggregate metrics for the most recent training run.
Response:
{
"epoch_history": [ /* array of EpochStats */ ],
"best_train_loss": 0.0821,
"best_val_loss": 0.0934,
"best_train_accuracy": 0.9872,
"best_val_accuracy": 0.9801,
"confusion_matrix": [[980,0,1,...], [0,1130,2,...], ...],
"class_labels": ["0","1","2","3","4","5","6","7","8","9"],
"tab_unlock": 31
}confusion_matrix[i][j] = number of samples with true class i predicted as class j.
All aggregate fields are null if no training has completed.
Downloads epoch history as a CSV file.
Response: Content-Type: text/csv, Content-Disposition: attachment; filename="epoch_history.csv"
CSV columns: epoch, train_loss, val_loss, train_accuracy, val_accuracy, elapsed_ms
Returns the list of available models and metadata for the selected model.
Query parameters:
model(optional): name of the model to select (stem of JSON filename, no extension)
Response:
{
"models": ["mnist", "first_network", "my_classifier"],
"selected": "mnist",
"model_info": {
"name": "mnist",
"input_type": { "type": "ImageGrayscale", "width": 28, "height": 28 },
"output_labels": ["0","1","2","3","4","5","6","7","8","9"],
"input_size": 784,
"output_size": 10
},
"tab_unlock": 31
}model_info is null when no model is selected or the model file cannot be loaded.
input_type values follow the ferrite-nn InputType enum serialization:
null— no metadata; frontend should offer manual mode selection{ "type": "Numeric" }— numeric inputs only{ "type": "ImageGrayscale", "width": 28, "height": 28 }— grayscale image{ "type": "ImageRgb", "width": 32, "height": 32 }— RGB image
Run inference on a model. Accepts multipart to support both numeric inputs and image file uploads.
Request: multipart/form-data
model(text): model nameinput_mode(text):"numeric"|"grayscale"|"rgb"input_width(text, optional): target width for image resize (default: 28)input_height(text, optional): target height for image resize (default: 28)inputs(text, optional): comma-separated floats for numeric modeimage_file(file, optional): PNG/JPEG/BMP/GIF for image modes
Image files are resized to input_width × input_height (Lanczos3 filter), converted to grayscale or RGB as appropriate, and normalized to [0.0, 1.0].
Response:
{
"result_type": "softmax",
"prediction": "7",
"confidence": 0.9821,
"all_scores": [
{ "label": "0", "score": 0.0012 },
{ "label": "7", "score": 0.9821 },
...
],
"raw_values": null
}result_type values:
"softmax"— multi-class;prediction= highest-confidence class label,confidence= its score,all_scores= all classes sorted by score"sigmoid"— binary;prediction="true"or"false",confidence= sigmoid output value"raw"— all other activations;raw_values= raw output vector,all_scores= indexed entries
Upload a ferrite-nn JSON model file and add it to the local trained_models/ directory.
Request: multipart/form-data
model_file(file):.jsonmodel file exported from ferrite-nn
Validates that the JSON contains a "layers" key. The filename stem becomes the model name (sanitized to alphanumeric, _, -).
Response: { "ok": true, "name": "imported_model_name" } or { "error": "..." }
Lists all models in the trained_models/ directory.
Response:
[
{
"name": "mnist",
"input_type": { "type": "ImageGrayscale", "width": 28, "height": 28 },
"output_labels": ["0","1","2","3","4","5","6","7","8","9"]
},
{
"name": "first_network",
"input_type": null,
"output_labels": null
}
]Download a trained model as a JSON file.
Path parameter: name — model name (no .json extension)
Response: Content-Type: application/json, Content-Disposition: attachment; filename="{name}.json"
Returns 404 if the model file does not exist.
When the FastAPI auth gateway is added in Phase 3, all /api/* requests will be routed through FastAPI first. The Rust service will trust a X-User-Id header injected by FastAPI after JWT validation. The response shapes defined here will not change — the auth layer is transparent to the frontend.
At that point, the frontend will also interact with FastAPI-only endpoints:
POST /auth/register,POST /auth/login,POST /auth/logoutPOST /auth/refreshGET /auth/oauth/github,GET /auth/oauth/google(and callbacks)GET /auth/me
These are documented separately in the ferrite-studio repository.