API Specification
Base URL: http://localhost:8765/api
All endpoints return JSON. Errors use standard HTTP status codes with a JSON body:
{
"detail": "Human-readable error message"
}
Runs
Create Run
POST /api/runs
Request Body:
{
"name": "My Simulation",
"template": "default",
"config": {
"general_settings": {
"network": "nsfnet",
"erlang_start": 50,
"erlang_stop": 200,
"erlang_step": 10,
"max_iters": 100
},
"topology_settings": {
"k_paths": 3
}
}
}
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
No |
User-friendly name (default: auto-generated) |
|
string |
No |
Base template to use (default: “default”) |
|
object |
Yes |
Config overrides to apply on top of template |
Response (201 Created):
{
"id": "a1b2c3d4e5f6",
"name": "My Simulation",
"status": "PENDING",
"created_at": "2024-01-15T10:00:00Z"
}
List Runs
GET /api/runs
GET /api/runs?status=RUNNING
GET /api/runs?limit=10&offset=0
Query Parameters:
Param |
Type |
Description |
|---|---|---|
|
string |
Filter by status (comma-separated for multiple) |
|
int |
Max results (default: 50, max: 100) |
|
int |
Pagination offset (default: 0) |
Response (200 OK):
{
"runs": [
{
"id": "a1b2c3d4e5f6",
"name": "My Simulation",
"status": "RUNNING",
"created_at": "2024-01-15T10:00:00Z",
"started_at": "2024-01-15T10:00:01Z",
"progress": {
"current_erlang": 100,
"total_erlangs": 16,
"current_iteration": 45,
"total_iterations": 100,
"percent_complete": 35.2
}
}
],
"total": 42,
"limit": 50,
"offset": 0
}
Get Run
GET /api/runs/{run_id}
Response (200 OK):
{
"id": "a1b2c3d4e5f6",
"name": "My Simulation",
"status": "RUNNING",
"config": { ... },
"created_at": "2024-01-15T10:00:00Z",
"started_at": "2024-01-15T10:00:01Z",
"completed_at": null,
"error_message": null,
"progress": {
"current_erlang": 100,
"total_erlangs": 16,
"current_iteration": 45,
"total_iterations": 100,
"percent_complete": 35.2,
"latest_metrics": {
"blocking_prob": 0.0234
}
}
}
Cancel Run
DELETE /api/runs/{run_id}
Cancels a PENDING or RUNNING run. For COMPLETED/FAILED/CANCELLED runs, this deletes the run and its artifacts.
Response (200 OK):
{
"id": "a1b2c3d4e5f6",
"status": "CANCELLED"
}
Response (409 Conflict): If run cannot be cancelled (e.g., already completed).
Stream Logs (SSE)
GET /api/runs/{run_id}/logs
Server-Sent Events stream of log content. Supports reconnection and catch-up.
Example (resuming from byte offset 4523):
GET /api/runs/abc123/logs?offset=4523
Query Parameters:
Param |
Type |
Description |
|---|---|---|
|
bool |
Include existing log content (default: true) |
|
int |
Byte offset to resume from (for reconnection) |
Events:
event: log
data: 2024-01-15 10:00:00 INFO Starting simulation...
event: log
data: 2024-01-15 10:00:01 INFO Loaded topology: nsfnet
event: heartbeat
data: {"offset": 4523}
event: end
data: COMPLETED
Reconnection Protocol:
Server sends
heartbeatevents every 15 seconds with current byte offsetClient stores the last received offset
On reconnect, client passes
?offset=<last_offset>to resume without gapsIf
offsetis stale (log rotated), server returns 410 Gone; client should reconnect withfrom_start=true
Client Behavior:
Use native
EventSourcewhich auto-reconnects on errorStore last heartbeat offset in component state
On reconnect, append
?offset=Xto URL
Stream Progress (SSE)
GET /api/runs/{run_id}/progress
Server-Sent Events stream of progress updates. Supports reconnection and catch-up.
Example (resuming from cursor):
GET /api/runs/abc123/progress?cursor=evt_00045
Query Parameters:
Param |
Type |
Description |
|---|---|---|
|
bool |
Include all progress events (default: true) |
|
string |
Opaque cursor for resumption (from last event) |
Events:
event: progress
data: {"type":"iteration","erlang":50,"iteration":45,"total_iterations":100,"metrics":{"blocking_prob":0.023},"cursor":"evt_00045"}
event: progress
data: {"type":"erlang_complete","erlang":50,"metrics":{"mean_blocking":0.022},"cursor":"evt_00046"}
event: heartbeat
data: {"cursor":"evt_00046"}
event: end
data: COMPLETED
Reconnection Protocol:
Each progress event includes a
cursorfieldServer sends
heartbeatevery 15 seconds with current cursorOn reconnect, pass
?cursor=<last_cursor>to resumeServer replays events after the cursor
Artifacts
List Artifacts
GET /api/runs/{run_id}/artifacts
GET /api/runs/{run_id}/artifacts?path=output
Query Parameters:
Param |
Type |
Description |
|---|---|---|
|
string |
Subdirectory to list (default: root) |
Response (200 OK):
{
"path": "output",
"entries": [
{
"name": "sim_20240115_100000",
"type": "directory",
"modified_at": "2024-01-15T10:10:00Z"
},
{
"name": "50_erlang.json",
"type": "file",
"size_bytes": 45678,
"modified_at": "2024-01-15T10:05:00Z"
}
]
}
Download Artifact
GET /api/runs/{run_id}/artifacts/{path}
Downloads a file. Security checks are enforced:
Security:
Path traversal blocked (403 Forbidden for
../or absolute paths)Symlinks are resolved via
realpath()and must resolve within the run directorySymlinks pointing outside run directory are rejected (403 Forbidden)
Response Headers:
Content-Type: application/octet-stream (or appropriate MIME type)
Content-Disposition: attachment; filename="50_erlang.json"
Content-Length: 45678
Error Responses:
403 Forbidden: Path traversal attempt or symlink escape404 Not Found: File does not exist
Preview Artifact
GET /api/runs/{run_id}/artifacts/{path}/preview
Returns file content for preview (text, JSON, CSV, images).
Query Parameters:
Param |
Type |
Description |
|---|---|---|
|
int |
Limit lines for text files (default: 1000) |
|
int |
Limit bytes (default: 1MB) |
Response (200 OK):
{
"path": "output/sim/0/50_erlang.json",
"type": "json",
"content": { ... },
"truncated": false
}
For images:
{
"path": "plots/blocking.png",
"type": "image",
"content_url": "/api/runs/{run_id}/artifacts/plots/blocking.png",
"width": 800,
"height": 600
}
Configs
List Templates
GET /api/configs/templates
Response (200 OK):
{
"templates": [
{
"name": "default",
"description": "Full-featured production baseline",
"path": "fusion/configs/templates/default.ini"
},
{
"name": "minimal",
"description": "Quick testing configuration",
"path": "fusion/configs/templates/minimal.ini"
}
]
}
Get Template
GET /api/configs/templates/{name}
Response (200 OK):
{
"name": "default",
"content": "[general_settings]\nnetwork = nsfnet\n...",
"parsed": {
"general_settings": {
"network": "nsfnet",
"erlang_start": 50
}
}
}
Validate Config
POST /api/configs/validate
Request Body:
{
"config": {
"general_settings": {
"network": "invalid_network",
"erlang_start": -10
}
}
}
Response (200 OK - Valid):
{
"valid": true,
"warnings": []
}
Response (200 OK - Invalid):
{
"valid": false,
"errors": [
{
"path": "general_settings.network",
"message": "Unknown network: 'invalid_network'. Available: nsfnet, usnet, ..."
},
{
"path": "general_settings.erlang_start",
"message": "Value must be positive"
}
],
"warnings": [
{
"path": "general_settings.max_iters",
"message": "Using default value: 100"
}
]
}
Get Config Schema
GET /api/configs/schema
Returns JSON Schema for configuration, enabling dynamic form generation.
Response (200 OK):
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"general_settings": {
"type": "object",
"properties": {
"network": {
"type": "string",
"enum": ["nsfnet", "usnet", "cost239"],
"description": "Network topology to simulate"
},
"erlang_start": {
"type": "number",
"minimum": 0,
"description": "Starting Erlang value"
}
}
}
}
}
Topology
List Topologies
GET /api/topology
Response (200 OK):
{
"topologies": [
{
"name": "nsfnet",
"nodes": 14,
"links": 21,
"description": "NSF Network topology"
}
]
}
Get Topology
GET /api/topology/{name}
Response (200 OK):
{
"name": "nsfnet",
"nodes": [
{"id": "0", "label": "Seattle", "x": 100, "y": 50},
{"id": "1", "label": "San Francisco", "x": 80, "y": 150}
],
"links": [
{"source": "0", "target": "1", "weight": 1100, "slots": 320}
]
}
Get Run Topology State
GET /api/runs/{run_id}/topology
Returns topology with current utilization from a running or completed simulation.
Response (200 OK):
{
"name": "nsfnet",
"nodes": [...],
"links": [
{
"source": "0",
"target": "1",
"slots_total": 320,
"slots_used": 156,
"utilization": 0.4875
}
]
}
System
Health Check
GET /api/health
Response (200 OK):
{
"status": "healthy",
"database": "connected",
"active_runs": 1
}
Version
GET /api/version
Response (200 OK):
{
"version": "6.1.0",
"api_version": "1",
"python_version": "3.11.5"
}
Error Responses
Status |
Meaning |
|---|---|
400 |
Bad Request - Invalid input |
403 |
Forbidden - Path traversal or access denied |
404 |
Not Found - Resource doesn’t exist |
409 |
Conflict - Operation not allowed in current state |
500 |
Internal Server Error |
Error Body:
{
"detail": "Run not found: xyz123"
}
Rate Limiting
No rate limiting for local use. If exposed over network (not recommended), consider adding.
CORS
Not needed for production (same origin). In development, Vite proxy handles it.