API routes¶
Every route of the TENSA server, rendered from its OpenAPI schema (TENSA 0.5.0). A running server serves the same schema at /openapi.json, with interactive pages at /docs (Swagger UI) and /redoc. The API guide says how the routes fit together, and the models page describes each request and response body.
Routes sit under /api. A request with a JSON body needs a Content-Type: application/json header. Every error the server raises itself is an RFC 7807 ProblemDetails document. A route that takes input also answers 422 with FastAPI's HTTPValidationError when the request does not match its schema; the tables below leave that row out unless a route gives its 422 a meaning of its own.
The WebSocket routes (/api/ws/...) are not part of an OpenAPI schema. The API guide describes them.
Contents¶
- Sessions
GET /api/sessions: List currently-active sessions.POST /api/sessions: Create a new session backed by a fresh ANDES worker subprocess.GET /api/sessions/{session_id}: Describe a session and count the call as activity.DELETE /api/sessions/{session_id}: Close a session and reap its worker subprocess.
- Cases and topology
POST /api/sessions/{session_id}/case: Load an ANDES case file (with optional addfiles) into a session.GET /api/sessions/{session_id}/topology: Get the current topology view of a session's loaded case.GET /api/sessions/{session_id}/topology/models/{model}/alterable_params: List parameter names ANDES will accept assrcfor an Alter disturbance on a given model.POST /api/sessions/{session_id}/reload: Re-load the current case to return to pre-setup state.GET /api/sessions/{session_id}/connectivity: Compute connected-component island count + per-island bus membership.
- Power flow
POST /api/sessions/{session_id}/pflow: Run power flow on the session's loaded case.GET /api/sessions/{session_id}/operating-point: Read the session's current operating point (bus V/θ) without running.
- Disturbances
GET /api/sessions/{session_id}/disturbances: List the disturbance specs currently recorded on the session.POST /api/sessions/{session_id}/disturbances: Register one or more disturbances on a pre-setup session.
- Elements
GET /api/topology/schema: Per-model parameter metadata for the add/edit forms.POST /api/sessions/{session_id}/elements: Add a topology element to a pre-setup session.PUT /api/sessions/{session_id}/elements/{model}/{idx}: Edit parameters on an existing pre-setup element.DELETE /api/sessions/{session_id}/elements/{model}/{idx}: Delete an element of a pre-setup session.POST /api/sessions/{session_id}/blank: Create an empty andes.System() for a session.POST /api/sessions/{session_id}/save: Write the current System to the workspace as xlsx or json.POST /api/sessions/{session_id}/undo-last-edit: Take back the last element added, changed or deleted.POST /api/sessions/{session_id}/redo-edit: Put back the edit that was taken back last.
- Parameter editing (clone)
POST /api/sessions/{session_id}/case/clone: Initialise the clone-on-write copy of the active case.PUT /api/sessions/{session_id}/case/clone/params/{model}/{idx}/{param}: Edit one whitelisted controller param on the clone (write + reload + setup).POST /api/sessions/{session_id}/case/clone/undo: Undo the most recent clone edit (restore prior file state + re-setup).POST /api/sessions/{session_id}/case/clone/redo: Re-apply the most recently undone clone edit.POST /api/sessions/{session_id}/case/clone/save-as: Write the clone to the workspace as a custom case.POST /api/sessions/{session_id}/case/clone/reset: Discard the clone and revert to the original case files.GET /api/sessions/{session_id}/case/clone/diff/{model}/{idx}: Diff the clone-file vs original-file values for one device.
- Time-domain simulation
POST /api/sessions/{session_id}/tds: Run a time-domain simulation (batch mode; stream live frames over the /ws/{session_id} WebSocket).GET /api/sessions/{session_id}/dae-variables: List the ANDES variables of the loaded case that a TDS run can record.GET /api/sessions/{session_id}/tds/controllers: List the controllers a TDS run takes and the devices of the loaded case they can command.POST /api/sessions/{session_id}/abort: Signal a cooperative abort of the active TDS run on a session.POST /api/response-metrics: Describe the response of sampled signals: nadir, rate of change, settling time, overshoot, damping.POST /api/comtrade: Write sampled signals as a COMTRADE (IEEE C37.111) record: a .cfg and an ASCII .dat in a .zip.
- Workspace
GET /api/workspace/files: List supported case files in the workspace root.POST /api/workspace/files: Add a case file to the workspace; the request body is the file.GET /api/workspace/layout: Read the SLD layout sidecar JSON adjacent to a case file.PUT /api/workspace/layout: Write the SLD layout sidecar JSON adjacent to a case file.
- Version
GET /api/version: Report the tensa and ANDES versions this server runs.
- Health
GET /api/health: Report that the server is up, with its versions, session load and code-cache state.
- Snapshots
POST /api/sessions/{session_id}/bundle/export: Export a reproducibility bundle (.zip) for the current session.POST /api/sessions/{session_id}/snapshot: Save the current operating point as a named snapshot.POST /api/sessions/{session_id}/snapshot/restore: Restore a previously-saved snapshot onto the session.GET /api/sessions/{session_id}/snapshots: List snapshots saved against the current case.DELETE /api/sessions/{session_id}/snapshot/{name}: Delete a snapshot by name.
- Bundles
POST /api/sessions/{session_id}/bundle/import: Import a reproducibility bundle (.zip) into the current session.
- Reports
GET /api/sessions/{session_id}/report: Render a human-readable report from PFlow or TDS results.
- Eigenvalue analysis
POST /api/sessions/{session_id}/eig: Run eigenvalue analysis (small-signal stability) on the session.GET /api/sessions/{session_id}/eig/modes/{mode_idx}/participation: Per-mode participation factor row for the given mode index.GET /api/sessions/{session_id}/eig/state-matrix.mat: Download EIG.As + EIG.mu as a .mat file.
- Continuation power flow
POST /api/sessions/{session_id}/cpf: Run continuation power flow (PV-curve / nose-curve) on the session.POST /api/sessions/{session_id}/cpf/qv: Run a single-bus QV-curve continuation on the session.
- State estimation
POST /api/sessions/{session_id}/se/measurements/generate: Generate the default SE measurement set from the converged PF solution.POST /api/sessions/{session_id}/se: Run static state estimation against the cached measurement set.
- PMU placement
GET /api/sessions/{session_id}/pmu: List currently-placed PMU instances on the session.POST /api/sessions/{session_id}/pmu: Place a PMU at the given bus on a pre-setup session.DELETE /api/sessions/{session_id}/pmu/{pmu_idx}: Remove a PMU from the pre-setup session.GET /api/sessions/{session_id}/pmu/{run_id}/export.csv: Download the PMU am/vm trajectories from the most recent TDS run.
- Profiles
POST /api/sessions/{session_id}/profiles/upload: Upload a CSV / XLSX hourly profile to the session's workspace.GET /api/sessions/{session_id}/profiles: List currently-staged TimeSeries profiles on the session.POST /api/sessions/{session_id}/profiles: Stage a TimeSeries profile assignment on a pre-setup session.DELETE /api/sessions/{session_id}/profiles/{profile_idx}: Remove a staged TimeSeries from the pre-setup session.
- Sweeps
POST /api/sessions/{session_id}/sweep: Start a sensitivity sweep.
- Jobs
GET /api/sessions/{session_id}/jobs: List jobs for a session.GET /api/sessions/{session_id}/jobs/{job_id}: Fetch one job by id.DELETE /api/sessions/{session_id}/jobs/{job_id}: Cancel a job.
- ANDES messages
GET /api/sessions/{session_id}/messages: Read what ANDES said while the session's commands ran.DELETE /api/sessions/{session_id}/messages: Forget the session's messages.
Sessions¶
GET /api/sessions¶
List currently-active sessions.
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json SessionList |
POST /api/sessions¶
Create a new session backed by a fresh ANDES worker subprocess.
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Successful Response | application/json SessionDescriptor |
| 422 | Body included a client-supplied session_id (rejected). | application/json ProblemDetails |
| 429 | max_sessions cap reached; retry after closing a session. | application/json ProblemDetails |
GET /api/sessions/{session_id}¶
Describe a session and count the call as activity.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json SessionDescriptor |
| 404 | Session not found or already closed. | application/json ProblemDetails |
DELETE /api/sessions/{session_id}¶
Close a session and reap its worker subprocess.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
| 204 | Successful Response |
Cases and topology¶
POST /api/sessions/{session_id}/case¶
Load an ANDES case file (with optional addfiles) into a session.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Request body (required)
Media type application/json: LoadCaseRequest
| Field | Type | Required | Description |
|---|---|---|---|
primary_path |
string | yes | Workspace-relative path to the primary case file. Supported formats: xlsx, raw, dyr, json, m. The substrate routes through ANDES's native readers; format is detected from the extension. Limits: min length 1. |
addfiles |
array of string or null | no | Optional list of workspace-relative addfile paths. PSS/E .raw (steady-state) and .dyr (dynamics) are paired via this mechanism; pass [.dyr path] when loading a .raw. |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json TopologySummary |
| 400 | Workspace path validation failed (traversal, absolute path, NUL byte, missing). | application/json ProblemDetails |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 422 | ANDES could not parse the case file. | application/json ProblemDetails |
GET /api/sessions/{session_id}/topology¶
Get the current topology view of a session's loaded case.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json TopologySummary |
| 404 | Session not found or no case loaded. | application/json ProblemDetails |
| 409 | No case has been loaded into this session yet. | application/json ProblemDetails |
GET /api/sessions/{session_id}/topology/models/{model}/alterable_params¶
List parameter names ANDES will accept as src for an Alter disturbance on a given model.
Best-effort introspection of the model's params ordered dict on
the loaded System. Filters to NumParam instances that are not
ExtParam - ANDES's own definition of "alterable" for the
alter() / set() contract.
The response order matches ANDES's internal declaration order on the
model class, which is also the order the UI's parameter dropdown
renders. Empty list when the model has no alterable params (no
NumParam declarations or all are ExtParam); the route is a 200
in that case.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes | |
model |
path | string | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json AlterableParamsResponse |
| 404 | Session not found or already closed, OR the requested model name is not present on the loaded System. |
application/json ProblemDetails |
| 409 | No case has been loaded into this session yet. | application/json ProblemDetails |
POST /api/sessions/{session_id}/reload¶
Re-load the current case to return to pre-setup state.
Calls andes.load(setup=False) again. Cost is honest - this is
a full re-parse, not a fast path. Documented in the OpenAPI summary so
callers can budget for multi-second latency on large cases.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json TopologySummary |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 409 | No case has been loaded into this session yet. | application/json ProblemDetails |
GET /api/sessions/{session_id}/connectivity¶
Compute connected-component island count + per-island bus membership.
Run ss.connectivity on the loaded System and return the
island summary.
This is post-run only.
The substrate does not extend the streaming pipeline's
VAR_GROUPS schema with per-frame island metadata; the UI calls
this endpoint manually (e.g., after a TDS run completes) via the
"Recompute connectivity" button on the SLD overlay.
The route requires a loaded case; ss.setup() is auto-called by
the wrapper if it hasn't already been (matching run_pflow /
run_eig behaviour). A SetupFailedError from the wrapper
surfaces as 422 with the standard reload-recovery hint.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json ConnectivityResponse |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 409 | No case has been loaded into this session yet. | application/json ProblemDetails |
| 422 | ANDES setup() failed before the connectivity check could run; call POST /api/sessions/{id}/reload to recover. | application/json ProblemDetails |
Power flow¶
POST /api/sessions/{session_id}/pflow¶
Run power flow on the session's loaded case.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Request body (required)
Media type application/json: PflowRunRequest
| Field | Type | Required | Description |
|---|---|---|---|
tolerance |
number or null | no | Convergence tolerance: the solver stops once the largest residual (mismatch), in pu, is below it. From 1e-12 to 0.01. ANDES's default is 1e-6. Limits: min 1e-12, max 0.01. |
max_iterations |
integer or null | no | Iteration limit. ANDES gives up once the count passes it. From 1 to 1000. ANDES's default is 25. Limits: min 1, max 1000. |
flat_start |
boolean or null | no | Start from 1 pu at angle 0 on every bus instead of the voltages and angles in the case. A bus with a generator holding its voltage still starts at that setpoint. Helps when the case's own starting point is far from the solution. Left out, the case's own setting stands; false turns off a flat start the case file asks for. |
enforce_q_limits |
boolean or null | no | Hold a PV or slack generator at qmin or qmax when its reactive power goes past one (PV to PQ switching). Without it the limits are reported and not enforced. Left out, the case's own setting stands; false turns off enforcement the case file asks for. |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json PflowResult |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 409 | No case has been loaded into this session. | application/json ProblemDetails |
| 422 | ANDES setup() failed, OR a previous EIG run mutated dae state (TDS.initialized=True). Either case requires POST /reload to recover. Also a setting outside its range (tolerance, max_iterations), which needs no reload: fix the request. |
application/json ProblemDetails |
GET /api/sessions/{session_id}/operating-point¶
Read the session's current operating point (bus V/θ) without running.
Return the System's current solved bus voltages/angles WITHOUT
re-running anything. After a TDS run the data grid otherwise sits empty
(only PF writes usePflowStore.lastRun); the client fetches this on
run completion to surface the final operating point. Read-only - does
not run a job or mutate dae state.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json PflowResult |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 409 | No case has been loaded into this session. | application/json ProblemDetails |
Disturbances¶
GET /api/sessions/{session_id}/disturbances¶
List the disturbance specs currently recorded on the session.
Return the wrapper's _disturbance_log for client sync.
Use case: after the client calls POST /sessions/{id}/reload, the
substrate's disturbance log is wiped (the new System has no
disturbances). The client can then call GET (now empty) to confirm,
re-POST the originals, and re-GET to verify they were re-accepted.
No 409 path - even on a freshly-created session with no case loaded the answer is well-defined (empty list).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json ListDisturbancesResponse |
| 404 | Session not found or already closed. | application/json ProblemDetails |
POST /api/sessions/{session_id}/disturbances¶
Register one or more disturbances on a pre-setup session.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Request body (required)
Media type application/json: AddDisturbancesRequest
| Field | Type | Required | Description |
|---|---|---|---|
disturbances |
array of FaultSpec or ToggleSpec or AlterSpec | yes | List of disturbance specifications. Discriminated by the kind field (fault, toggle, alter). Limits: min items 1. |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json AddDisturbancesResponse |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 409 | Session has already been committed (PF or TDS has run); call POST /api/sessions/{id}/reload to return to pre-setup. | application/json ProblemDetails |
| 422 | ANDES rejected one of the disturbance specs (e.g., bad bus idx). | application/json ProblemDetails |
Elements¶
GET /api/topology/schema¶
Per-model parameter metadata for the add/edit forms.
Static endpoint - the schema mirrors a compile-time table on the wrapper.
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json TopologySchema |
POST /api/sessions/{session_id}/elements¶
Add a topology element to a pre-setup session.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Request body (required)
Media type application/json: AddElementRequest
| Field | Type | Required | Description |
|---|---|---|---|
model |
string | yes | ANDES model class name. It must be one of the buildable models listed by GET /api/topology/schema (buses, lines, generators, loads, shunts, exciters, governors, the ESD1 battery, and other controllers). Unknown models are rejected with 422. Limits: min length 1. |
params |
object (names to number or integer or string or boolean) | yes | Flat dict of model parameters. Keys are validated against the per-model whitelist; values pass through to ss.add(). A reference to another device (bus, gen, syn) may be sent as text or as a number: it is matched to the idx the case holds. A request that leaves out a parameter ANDES has no default for is rejected with 422 and adds nothing. Required keys vary per model - query GET /api/topology/schema for the live form metadata. |
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Successful Response | application/json ElementCreated |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 409 | Session has already been committed (PF or TDS has run); call POST /api/sessions/{id}/reload to return to pre-setup. | application/json ProblemDetails |
| 413 | Request body exceeded the 65536-byte cap on topology mutation endpoints. | application/json ProblemDetails |
| 422 | ANDES rejected the add() call (unknown model, missing required param, invalid bus reference, etc.) or the wrapper-side whitelist found unknown param keys. | application/json ProblemDetails |
PUT /api/sessions/{session_id}/elements/{model}/{idx}¶
Edit parameters on an existing pre-setup element.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes | |
model |
path | string | yes | |
idx |
path | string | yes |
Request body (required)
Media type application/json: EditElementRequest
| Field | Type | Required | Description |
|---|---|---|---|
params |
object (names to number or integer or string or boolean) | yes | Subset of model parameters to overwrite. Each key must be in the per-model whitelist; idx / name are explicitly rejected (create a new element instead). |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json TopologyEntry |
| 404 | Session not found, or no element of the given model+idx exists in the loaded System. | application/json ProblemDetails |
| 409 | Session has already been committed; call POST /api/sessions/{id}/reload to return to pre-setup. | application/json ProblemDetails |
| 413 | Request body exceeded the 65536-byte cap on topology mutation endpoints. | application/json ProblemDetails |
| 422 | ANDES rejected the parameter write (e.g., unknown param key, wrong type, attempt to edit idx/name). | application/json ProblemDetails |
DELETE /api/sessions/{session_id}/elements/{model}/{idx}¶
Delete an element of a pre-setup session.
Deletes any element of the loaded system: one the case file brought as
well as one added since. The case file is not written. The delete is one
edit, which POST /sessions/{id}/undo-last-edit takes back whole, with
the dependents and the disturbances that went with it. The disturbances
committed for the next run that act on nothing deleted are kept.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes | |
model |
path | string | yes | |
idx |
path | string | yes | |
cascade |
query | boolean | no | Delete with the element everything that cannot stay without it: the elements that depend on it and the disturbances that act on any of them. Without it, a delete that would leave any of those behind is refused with the list. Default false. |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json DeleteElementResponse |
| 404 | Session not found, or no element of the given model+idx exists in the loaded System. | application/json ProblemDetails |
| 409 | Session has already been committed (PF or TDS has run); call POST /api/sessions/{id}/reload to return to pre-setup. | application/json ProblemDetails |
| 422 | Deletion is refused. Two sub-cases share this status: (a) other elements depend on the element, or disturbances act on it or on one of those, and cascade is not set (body matches DeleteBlockedResponse); (b) unknown model name, or the device is a committed disturbance (body matches ProblemDetails). |
application/json DeleteBlockedResponse |
POST /api/sessions/{session_id}/blank¶
Create an empty andes.System() for a session.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Successful Response | application/json BlankSystemResponse |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 409 | A System is already loaded on this session; reload or open a fresh session first. | application/json ProblemDetails |
POST /api/sessions/{session_id}/save¶
Write the current System to the workspace as xlsx or json.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Request body (required)
Media type application/json: SaveCaseRequest
| Field | Type | Required | Description |
|---|---|---|---|
filename |
string | yes | Workspace-relative output filename. Extension must match format (.xlsx for xlsx, .json for json, .raw for raw). The file name must be portable to Windows: no :, trailing dot or space, or device names such as CON or nul.xlsx. Limits: min length 1. |
format |
string | yes | Output format. xlsx is the ANDES-native Excel layout. json is the ANDES JSON serialization. raw is PSS/E v33 emitted by the substrate's hand-rolled writer; it covers the power-flow data: Bus, PQ loads, Shunt, PV and Slack generators, Line, and 2W transformers. 3W transformers and other PSS/E sections are emitted as empty terminators. A .raw holds no dynamic data, so the dynamic models of the case (machines, exciters, a ZIP load) are left out: save as xlsx or json to keep them. One of xlsx, json, raw. |
overwrite |
boolean | no | When true, overwrites an existing file at the same path. Default false returns 409 if the file exists. Default false. |
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Successful Response | application/json SaveCaseResponse |
| 404 | Session not found or no case loaded. | application/json ProblemDetails |
| 409 | A file at the given filename exists and overwrite was not set, OR no case is loaded on this session. |
application/json ProblemDetails |
| 413 | Body exceeded the 64 KB cap. | application/json ProblemDetails |
| 422 | Filename failed validation (path traversal, mismatched extension, or unwriteable target), or filename is the case file this session loaded and the system holds disturbances, which would be written into it. |
application/json ProblemDetails |
POST /api/sessions/{session_id}/undo-last-edit¶
Take back the last element added, changed or deleted.
Takes back the newest edit made since the case was loaded, whether it
added an element, changed its params or deleted it (with everything a
cascade took). The edits before it stay, and so do the committed
disturbances; the ones a delete had removed come back with it. The
summary's undo names the edit that is now the newest, and redo
the one just taken back.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json TopologySummary |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 409 | Session has been committed (PF / TDS already ran); reset the run before undoing. | application/json ProblemDetails |
| 422 | No edits to undo on this session, or the edit added an element a committed disturbance acts on. | application/json ProblemDetails |
POST /api/sessions/{session_id}/redo-edit¶
Put back the edit that was taken back last.
Applies again the edit POST /sessions/{id}/undo-last-edit took back
last. Any new edit empties what there is to redo.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json TopologySummary |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 409 | Session has been committed (PF / TDS already ran); reset the run before redoing. | application/json ProblemDetails |
| 422 | No edits to redo on this session (body matches ProblemDetails), or the edit is a delete that something has come to depend on since (body matches DeleteBlockedResponse). |
application/json DeleteBlockedResponse |
Parameter editing (clone)¶
POST /api/sessions/{session_id}/case/clone¶
Initialise the clone-on-write copy of the active case.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json CloneInitResponse |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 409 | No case is loaded, the session is busy with an in-flight job, or a sweep holds the session lock. | application/json ProblemDetails |
| 422 | The loaded case cannot be cloned (e.g. a blank session). | application/json ProblemDetails |
PUT /api/sessions/{session_id}/case/clone/params/{model}/{idx}/{param}¶
Edit one whitelisted controller param on the clone (write + reload + setup).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes | |
model |
path | string | yes | |
idx |
path | string | yes | |
param |
path | string | yes |
Request body (required)
Media type application/json: CloneEditRequest
| Field | Type | Required | Description |
|---|---|---|---|
value |
number or integer or string or boolean | yes | New value for the parameter. Written to the clone case file; ANDES re-reads it on the subsequent load(setup=False) → setup() cycle. Some params are per-unit-normalised at setup, so the read-back live value may differ from the file value. |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json CloneEditResponse |
| 404 | Session not found, or no device of the given model+idx exists. | application/json ProblemDetails |
| 409 | The session is busy with an in-flight job (e.g. TDS streaming) or a sweep holds the lock; retry when it completes. | application/json ProblemDetails |
| 413 | Body exceeded the 64 KB cap. | application/json ProblemDetails |
| 422 | The (model, param) is not a whitelisted dynamic-controller param, the param is read-only in the loaded format, or the clone writer rejected the edit (malformed file / setup failed). | application/json ProblemDetails |
POST /api/sessions/{session_id}/case/clone/undo¶
Undo the most recent clone edit (restore prior file state + re-setup).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json CloneEditResponse |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 409 | Session busy with an in-flight job. | application/json ProblemDetails |
| 422 | Nothing to undo. | application/json ProblemDetails |
POST /api/sessions/{session_id}/case/clone/redo¶
Re-apply the most recently undone clone edit.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json CloneEditResponse |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 409 | Session busy with an in-flight job. | application/json ProblemDetails |
| 422 | Nothing to redo. | application/json ProblemDetails |
POST /api/sessions/{session_id}/case/clone/save-as¶
Write the clone to the workspace as a custom case.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Request body (required)
Media type application/json: CloneSaveAsRequest
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Workspace-relative case name (stem only - no extension, no path separators or traversal). The clone's files are written as <name>.<ext> for each cloned format. |
overwrite |
boolean | no | When false (the default), save-as REFUSES to overwrite an existing workspace file (a 422) - this protects the loaded original case, which lives in the same workspace, from being silently clobbered. Pass true to intentionally overwrite an existing case. Default false. |
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Successful Response | application/json CloneSaveAsResponse |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 409 | Session busy with an in-flight job. | application/json ProblemDetails |
| 413 | Body exceeded the 64 KB cap. | application/json ProblemDetails |
| 422 | No clone to save, an invalid / traversal name, the workspace is not configured, or the name collides with an existing workspace file (pass overwrite=true to replace it). | application/json ProblemDetails |
POST /api/sessions/{session_id}/case/clone/reset¶
Discard the clone and revert to the original case files.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json CloneResetResponse |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 409 | Session busy with an in-flight job. | application/json ProblemDetails |
GET /api/sessions/{session_id}/case/clone/diff/{model}/{idx}¶
Diff the clone-file vs original-file values for one device.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes | |
model |
path | string | yes | |
idx |
path | string | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json CloneDiffResponse |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 409 | Session busy with an in-flight job. | application/json ProblemDetails |
| 422 | The model is not a whitelisted dynamic-controller class. | application/json ProblemDetails |
Time-domain simulation¶
POST /api/sessions/{session_id}/tds¶
Run a time-domain simulation (batch mode; stream live frames over the /ws/{session_id} WebSocket).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Request body (required)
Media type application/json: TdsRunRequest
| Field | Type | Required | Description |
|---|---|---|---|
tf |
number | yes | Final simulation time, in seconds. Must be > 0. Limits: above 0. |
h |
number or null | no | Integration step size, in seconds, applied as ANDES TDS.config.tstep. With the default trapezoidal integrator this is the fixed step; None keeps the ANDES default (1/30 s). The qndf integrator picks its own step and ignores it; bound its step with tds_config_overrides.max_step instead. Limits: above 0. |
vars |
array of string or null | no | Optional selector for which variable groups appear as columns in each per-step Arrow record batch on the streaming path. bus_v covers bus voltage magnitudes and angles; gen_state adds generator rotor angle delta and per-unit speed omega for every member of the ANDES SynGen group (GENROU / GENCLS / PLBVFU1); gen_power adds their electrical Pe / Qe; line_flow adds active and reactive power at each line's bus1 terminal; load_pq adds each PQ load's consumption. Unknown values are rejected with 422; an empty list is rejected with 422. The batch path (POST /tds) ignores this field at runtime: it returns the values of dae_vars instead, and accepts this field for symmetry with the WebSocket start_tds config so generated clients can share one request shape. Defaults to ["bus_v", "gen_state"] when omitted on the streaming path. Limits: min items 1. |
dae_vars |
array of string or null | no | ANDES variables to record, by the names dae.x_name and dae.y_name give them (omega GENROU 1, vf GENROU 2); GET /sessions/{id}/dae-variables lists them. A batch run returns their values in traces, at every step ANDES takes, with no scaling. A name that is not a variable of the loaded case is refused with 422 before anything runs. The streaming start_tds frame takes the same field and adds one column per name, named exactly so. Limits: max items 1000. |
integrator |
string | no | DAE integrator. "trapezoidal" (default) maps to ANDES's fixed-step Implicit Trapezoidal Method (ss.TDS.config.method = "trapezoid"). "qndf" selects the variable-order, variable-step QNDF (NDF) method and forces fixt = 0 so ANDES enables LTE-driven step control. Combine integrator="qndf" with the Auto preset (rtol=1e-3, atol=1e-6, max_step=0.05) by passing the values via tds_config_overrides. One of trapezoidal, qndf. Default "trapezoidal". |
controllers |
array of DroopController or FfrController or null | no | Controllers that act while the run goes: each reads the frequency once a sample period and sets the power of a battery or another distributed generation device, on top of the device's own set-point. Two kinds, picked by type: droop (power in proportion to the frequency deviation beyond a dead band) and ffr (a fixed power, once, for a set time when the frequency leaves a threshold or moves too fast). Powers are in MW, positive discharging, and frequencies in Hz. GET /sessions/{id}/tds/controllers lists the devices that can be named. A controller that names a device the case does not have is refused with 422 before anything runs. What each did comes back in the result's controllers; when the run ends the devices' inputs are as they were. A later run that names the same controllers and goes on from where this one stopped (a larger tf) keeps their state, so an ffr that has fired does not fire again. After a reload or a restored snapshot they start afresh, and it can. The streaming start_tds frame takes the same field. Sweeps do not take controllers. Limits: max items 32. |
tds_config_overrides |
object (names to number) or null | no | Optional adaptive-integrator tolerance overrides. Supported keys are rtol (→ ss.TDS.config.reltol), atol (→ ss.TDS.config.abstol) and max_step (→ ss.TDS.config.dtmax), or the name of any ss.TDS.config field. An unknown key is refused with 422. So is a value that ANDES would take and then misbehave on: tstep must be finite and greater than 0, max_step (dtmax) finite and not negative (0 lets ANDES choose), fixt 0 or 1, and every other value a finite number. The tolerances have no effect when integrator="trapezoidal" (the fixed-step path ignores reltol/abstol and uses h for stepping). |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json TdsBatchResult |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 409 | No case has been loaded into this session. | application/json ProblemDetails |
| 422 | ANDES setup() failed (call /reload to recover), or the request names a step size or an override ANDES must not be given, or a controller the loaded case cannot bind (nothing was written, so there is nothing to reload). | application/json ProblemDetails |
GET /api/sessions/{session_id}/dae-variables¶
List the ANDES variables of the loaded case that a TDS run can record.
The states and algebraic variables ANDES keeps for the loaded case's
devices, named as dae.x_name / dae.y_name name them, for the
dae_vars of a TDS request. Needs no setup, so asking does not close the
case to new disturbances. A static generator that a dynamic one replaces
keeps no algebraic variables in a TDS run and is left out. With no case
loaded the list is empty (a 200, as for the disturbance list).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes | |
q |
query | string or null | no | Words that must all appear in the variable's name, whatever their case: omega gen finds omega GENROU 1. Limits: max length 200. |
kind |
query | string or null | no | x for state variables only, y for algebraic ones only. One of x, y. |
model |
query | string or null | no | Only this ANDES model's variables (GENROU). Limits: max length 100. |
limit |
query | integer | no | Page size. Limits: min 1, max 1000. Default 100. |
offset |
query | integer | no | Matches to skip. Limits: min 0. Default 0. |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json DaeVariableList |
| 404 | Session not found or already closed. | application/json ProblemDetails |
GET /api/sessions/{session_id}/tds/controllers¶
List the controllers a TDS run takes and the devices of the loaded case they can command.
The kinds of controller a TDS request's controllers can name (a
frequency droop, a fast frequency response) and the devices of the loaded
case each can command, with the ANDES variables to record to watch one at
work. Needs no setup, so asking does not close the case to new
disturbances. With no case loaded the list of devices is empty (a 200, as
for the variable list).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json TdsControllerCatalogue |
| 404 | Session not found or already closed. | application/json ProblemDetails |
POST /api/sessions/{session_id}/abort¶
Signal a cooperative abort of the active TDS run on a session.
Set the session's abort event. Cooperatively terminates an active
streaming or batch run_tds invocation at the next callpert
tick. Returns 200 immediately - the actual TDS exit is asynchronous
on the worker.
Session-scoped (not run-scoped): v0.2 has at most one active run per
session, mirroring SessionManager.signal_abort's API. Calling
abort while no TDS is running is a 200 no-op (the event is set but
never consumed; subsequent runs will see and clear it).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json AbortResponse |
| 404 | Session not found or already closed. | application/json ProblemDetails |
POST /api/response-metrics¶
Describe the response of sampled signals: nadir, rate of change, settling time, overshoot, damping.
For each series: its initial and final value, its peak and nadir, the
steepest rate of change over rocof_window, the time it takes to settle,
how far it overshoots, and the damping ratio and frequency of its oscillation.
A series that cannot be described (too few samples, a window outside it)
carries an error and the rest of the request is answered. The definitions
are in each field's description. Needs no session: send the columns of a
streamed run, or the traces of a batch run.
Request body (required)
Media type application/json: ResponseMetricsRequest
| Field | Type | Required | Description |
|---|---|---|---|
series |
array of MetricsSeries | yes | The signals to describe, each on its own timeline. Limits: min items 1, max items 64. |
t_start |
number or null | no | Start of the window the metrics are read over, in seconds. A bound that falls between two samples is interpolated. Defaults to the first sample. |
t_end |
number or null | no | End of the window, in seconds. Defaults to the last sample. |
settling_band |
number | no | The settling band as a fraction of the signal's largest distance from its final value: 0.02 is 2 %. Limits: above 0, below 1. Default 0.02. |
rocof_window |
number | no | Width in seconds the rate of change is measured over: the steepest slope of the line from y(s) to y(s + rocof_window). Cut to the window when the window is shorter. Limits: above 0. Default 0.5. |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json ResponseMetricsResponse |
| 413 | The body is larger than 62 MiB, more than a request within the limits takes (1000000 samples in all). It is refused before it is read. | application/json ProblemDetails |
| 422 | The body is malformed, or a limit is exceeded: more than 64 series, more than 200000 samples in one, or a setting out of range. A series that is only too short to describe is not an error: it is answered with its own error. |
application/json ProblemDetails |
POST /api/comtrade¶
Write sampled signals as a COMTRADE (IEEE C37.111) record: a .cfg and an ASCII .dat in a .zip.
Each signal becomes an analog channel named and given the unit the request
gives it, scaled over its own range onto the integers a 1999 data file holds,
so a channel resolves about 1 / 200 000 of its range. The record declares a
sampling rate only when the samples are evenly spaced; otherwise a reader goes
by the time stamps, as it must for a run with an event in it. The values are
the caller's own (per unit, MW, radians), and for a time-domain run they are
phasor quantities, not instantaneous waveforms. Needs no session: send the
columns of a streamed run, or t and variables of a batch run's
traces.
Request body (required)
Media type application/json: ComtradeExportRequest
| Field | Type | Required | Description |
|---|---|---|---|
t |
array of number | yes | Sample times in seconds, not decreasing, at least one. Times need not be evenly spaced: the record carries a time stamp per sample. |
channels |
array of ComtradeChannelSeries | yes | The signals, each with one value per time in t. At most 5000000 values in all (channels times samples). Limits: min items 1. |
name |
string | no | File name the record's two files share: the archive holds <name>.cfg and <name>.dat. 1-64 chars of [A-Za-z0-9._-] starting with an alphanumeric, not ending in a dot, and not a Windows device name (CON, NUL, COM1, ...). Default "tensa". |
station |
string | no | The record's station name, for instance the case the run was made on. Reduced to ASCII without commas and cut to 64 characters. Limits: max length 200. Default "". |
device |
string or null | no | The record's recording device, for instance a name for the run. Reduced as station is. Defaults to TENSA and its version. Limits: max length 200. |
frequency_hz |
number | no | The system's nominal frequency in Hz, the record's line frequency. Limits: above 0. Default 60.0. |
start_time |
string (date-time) or null | no | The date and time simulated time zero stands for (ISO 8601). The record's first date line is this plus the first time in t. The format carries no time zone, so the clock reading is written as given. Defaults to the server's local time when the request arrives. |
trigger_t |
number or null | no | Simulated time of the trigger point in seconds, for instance when a fault is applied; it must lie inside the record. Defaults to the first time in t. |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | A .zip holding <name>.cfg and <name>.dat: the 1999 revision of the standard, ASCII data, one analog channel per signal and a time stamp per sample. |
application/zip |
| 413 | The request holds more than 5000000 values (channels times samples). | application/json ProblemDetails |
| 422 | The body is malformed, or the signals make no record: no sample, times that decrease, a channel whose length is not that of t, a trigger_t outside the record, or a name that is not a plain file name. |
application/json ProblemDetails |
Workspace¶
GET /api/workspace/files¶
List supported case files in the workspace root.
Return a sorted list of files in the workspace root whose extension matches the supported set. Non-recursive; excludes hidden files and symlinks.
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json WorkspaceFileList |
POST /api/workspace/files¶
Add a case file to the workspace; the request body is the file.
Store the request body as <workspace>/<name>.
The body is the file itself (curl --data-binary @ieee14.raw), written
atomically, so a half-written case never shows up in the lister. The name goes
through the portable-name rule, and a name that exists is never replaced
unless overwrite=true: the check happens when the file is put in place, so
two uploads of one name cannot both win.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
name |
query | string | yes | File name to store the upload under, in the workspace root. A single name (no directories) with one of the case extensions (.xlsx, .raw, .dyr, .json, .m). |
overwrite |
query | boolean | no | Replace a file of the same name. When false (the default) an existing file is left alone and the request answers 409. Default false. |
Request body (required)
The file's bytes, verbatim. Not multipart form data: a multipart/* body is refused with 415.
Media type application/octet-stream: string (binary)
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Successful Response | application/json UploadedWorkspaceFile |
| 400 | The name is unsafe or is not a single file name: a path, a leading dot, a character or device name Windows would misread (CON.raw, a:b.raw), a trailing dot or space, or too long. |
application/json ProblemDetails |
| 409 | The workspace already has a file of that name and overwrite was not set (or the name is a directory). |
application/json ProblemDetails |
| 413 | The body is larger than 32 MiB. | application/json ProblemDetails |
| 415 | The body was sent as multipart form data instead of the raw file. | application/json ProblemDetails |
| 422 | The extension is not one of .xlsx, .raw, .dyr, .json, .m, the name is a layout sidecar (<case>.layout.json, written by PUT /workspace/layout), or the file is empty. |
application/json ProblemDetails |
| 500 | The file could not be written (full disk, permissions). | application/json ProblemDetails |
GET /api/workspace/layout¶
Read the SLD layout sidecar JSON adjacent to a case file.
Read the layout sidecar for case_path.
A missing sidecar is the normal first-run state for any case, so it is
NOT an error: the endpoint returns 200 with a JSON null body (the
web client already maps "no sidecar" to a null layout; previously this
was a 404, which browsers log as a console error on every case open).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
case_path |
query | string | yes | Workspace-relative path of the case file the sidecar is paired with. The sidecar is read from <case_path>.layout.json. |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json SidecarLayout or null |
| 400 | Workspace path validation failed. | application/json ProblemDetails |
| 422 | Sidecar exists but does not match the SidecarLayout schema. | application/json ProblemDetails |
PUT /api/workspace/layout¶
Write the SLD layout sidecar JSON adjacent to a case file.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
case_path |
query | string | yes | Workspace-relative path of the case file the sidecar is paired with. The sidecar is written to <case_path>.layout.json. |
Request body (required)
Media type application/json: SidecarLayout
| Field | Type | Required | Description |
|---|---|---|---|
schema_version |
string | yes | Schema version of the document. The server writes and answers with "2"; a version 1 document ("1", "1.0") is accepted and upgraded. Limits: min length 1. |
andes_version |
string | yes | ANDES version the layout was saved against (e.g., "2.0.0"). Recorded for diagnosis; not validated on read. Limits: min length 1. |
coordinates |
object (names to BusCoord) | yes | Per-bus coordinates, keyed by bus idx (stringified). Buses missing from this dict fall back to the renderer's auto-layout. |
non_bus_coordinates |
object (names to object (names to LayoutDeviceCoord)) | no | Per-non-bus-element coordinates, two-level dict keyed by ANDES model class (e.g., PV, GENROU, PQ, Shunt) OR by UI category (generator, load, shunt), then by element idx (stringified). The writer emits BOTH the model-class-keyed entry and the UI-category-keyed entry for every non-bus element so kind-edits (e.g., PV → GENROU) survive: the model-class entry becomes orphaned but the UI-category entry still resolves on read. Optional + additive - old sidecars without this field read as {} and the renderer falls back to kind-default offsets. |
controller_coordinates |
object (names to object (names to BusCoord)) | no | Positions of controllers that were placed on their own, keyed by ANDES model class (EXST1, TGOV1), then by idx. A controller with no entry is drawn docked beside the device it acts on and moves with it. |
units |
object (names to LayoutUnit) | no | Per generating unit (a generator with its machine and their controllers), keyed by the idx of the unit's generator symbol, the same idx its position has under generator in non_bus_coordinates. |
busbars |
object (names to LayoutBusbar) | no | The bar of a bus where it differs from the default, keyed by bus idx. |
branches |
object (names to object (names to LayoutBranchRoute)) | no | How branches are drawn, keyed by line or transformer, then by idx. A branch with no entry is routed automatically. |
label_offsets |
object (names to object (names to LayoutLabelOffset)) | no | Labels that were moved, keyed like non_bus_coordinates with bus, line and transformer as further outer keys, then by idx. |
connections |
object (names to object (names to LayoutConnection)) | no | Where the connector of a generator, load or shunt attaches and, where it was drawn by hand, the points it runs through, keyed like non_bus_coordinates. A device with no entry has its connector worked out from where it sits. |
figure |
object (names to boolean or integer or number or string) | no | Display settings of this diagram and of a figure made from it (for example a monochrome style, a line width, which labels are shown), by name. The diagram reads connector_style: straight (the default) draws the connector of a generator, load or shunt to its bus as one line, elbow with one right angle. Values are booleans, finite numbers or short text; at most 64 settings. |
last_modified |
string | yes | ISO 8601 timestamp recorded by the client at save time. The server does NOT regenerate this on write; it stores the value the client sent so collaborative-edit conflict detection (a future feature) has a single source of truth. Limits: min length 1. |
Responses
| Status | Meaning | Body |
|---|---|---|
| 204 | Successful Response | |
| 400 | Workspace path validation failed. | application/json ProblemDetails |
| 413 | The body is over the 2 MiB cap on a layout, or the layout in it is. The cap is measured on the layout as the server stores it, compact JSON with every field written out, so a body that leaves fields to their defaults can be under the cap and its layout over. Nothing is written. A layout this route takes is one a save under a new name, a snapshot and a bundle can all carry. | application/json ProblemDetails |
| 422 | Body did not validate against SidecarLayout. | application/json ProblemDetails |
Version¶
GET /api/version¶
Report the tensa and ANDES versions this server runs.
Static endpoint: the versions are read from package metadata and never change while the server runs.
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json VersionInfo |
Health¶
GET /api/health¶
Report that the server is up, with its versions, session load and code-cache state.
Reads the session registry and a few files; nothing here waits for a worker.
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json HealthResponse |
Snapshots¶
POST /api/sessions/{session_id}/bundle/export¶
Export a reproducibility bundle (.zip) for the current session.
Assemble a reproducibility-bundle .zip and return it as a stream.
The substrate gathers the case file (verbatim or canonical xlsx),
builds the manifest, and zips the lot together with the
request-body-supplied disturbances / sim_params / results.csv and the
diagram's layout (the request's, else the one saved beside the case).
The response is the raw zip bytes with Content-Type: application/zip
and a Content-Disposition header that suggests a sensible
filename.
Snapshots (dill payloads) are explicitly NOT in the bundle: they're version-locked and undermine portability.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Request body (required)
Media type application/json: BundleExportRequest
| Field | Type | Required | Description |
|---|---|---|---|
disturbances |
array of FaultSpec or ToggleSpec or AlterSpec | no | Disturbance specs (the discriminated union) the user composed in the timeline editor. Empty list when no disturbances were registered; disturbances.json is omitted from the bundle in that case. |
sim_params |
BundleSimParams or null | no | Last TDS run's sim params. null when no run has fired yet; sim_params.json is omitted from the bundle in that case. |
results_csv |
string or null | no | Last TDS run's results as a long-form CSV body. null when no run has fired yet OR the caller chose not to ship the body. When omitted, results.csv is absent from the bundle but the run is still reproducible from case + disturbances + sim_params. |
run_id |
string or null | no | Run id of the most recent TDS run. Surfaced in the manifest for cross-referencing with run-history exports. |
layout |
SidecarLayout or null | no | The diagram's layout as the client shows it, written to the bundle as layout.json so an import opens with the same picture. null uses the layout saved beside the case file; with neither, the bundle holds no layout.json. One over the cap PUT /workspace/layout holds a layout to is refused (413). |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Bundle stream. Body is a .zip containing case + disturbances.json + sim_params.json + results.csv + layout.json + manifest.json (each optional except case + manifest). Snapshots are NOT included. |
application/zip |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 409 | No case has been loaded into this session yet. | application/json ProblemDetails |
| 413 | layout is over the 2 MiB cap PUT /workspace/layout holds a layout to, measured as the server stores it (compact JSON with every field written out). |
application/json ProblemDetails |
| 422 | The substrate could not produce a canonical xlsx export for the dirty case (elements ANDES can't roundtrip), or a case file has a name the importer would refuse (a Windows device name such as CON, a ':', a leading dot, a trailing dot or space), or the case has more case files than an import accepts (16); the message names the file or the count. | application/json ProblemDetails |
POST /api/sessions/{session_id}/snapshot¶
Save the current operating point as a named snapshot.
Save snapshot endpoint.
Writes sidecar JSON metadata under
<workspace>/snapshots/<case_basename>/<name>.json and, when
include_dill is true, ANDES's andes.utils.snapshot.save_ss blob
beside it as <name>.dill. The metadata holds the diagram's layout,
so a restore brings the placement back with the operating point.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Request body (required)
Media type application/json: SaveSnapshotRequest
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Snapshot name. 1-64 chars of [A-Za-z0-9._-] starting with an alphanumeric, not ending in a dot, and not a Windows device name (CON, NUL, COM1, ...). Names are unique per case; collisions return 409 unless force=true. |
force |
boolean | no | When True, overwrite any existing snapshot under the same name. Default False rejects collisions with 409 so the UI can prompt the user. Default false. |
include_dill |
boolean | no | When True, also write the full System state as a dill blob (andes.utils.snapshot.save_ss) beside the metadata, so a later restore can opt in to use_dill_optimization. Costs a couple of seconds and 2-3 MB per save, and the blob only loads on the same ANDES major.minor. A snapshot saved after the power flow but before the TDS was initialised on a case with dynamic models cannot be loaded by ANDES 2.0; its restore falls back to replay. Default False writes the metadata only. Default false. |
layout |
SidecarLayout or null | no | The diagram's layout as the client shows it. It is kept in the snapshot and put back beside the case when the snapshot is restored. null keeps the layout saved beside the case file, if there is one. One over the cap PUT /workspace/layout holds a layout to is refused (413). |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json SaveSnapshotResponse |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 409 | Snapshot name collision (re-issue with force=true) OR no case loaded yet. |
application/json ProblemDetails |
| 413 | layout is over the 2 MiB cap PUT /workspace/layout holds a layout to, measured as the server stores it (compact JSON with every field written out). |
application/json ProblemDetails |
| 422 | Invalid snapshot name or save-time failure inside ANDES. | application/json ProblemDetails |
POST /api/sessions/{session_id}/snapshot/restore¶
Restore a previously-saved snapshot onto the session.
Restore snapshot endpoint.
By default reloads the case, replays the snapshot's
disturbance_log from the sidecar JSON, and re-runs setup +
PFlow.run. With use_dill_optimization it first tries to
substitute the dill-loaded System (when the snapshot has a blob and
the ANDES version matches) and falls back to the replay otherwise.
The diagram's layout the snapshot holds is put back beside the case
file and returned.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Request body (required)
Media type application/json: RestoreSnapshotRequest
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Snapshot name to restore. |
use_dill_optimization |
boolean | no | Default False restores by reloading the case, re-adding the recorded disturbances, and re-running setup and the power flow, which works on any ANDES version. When True, try the dill blob first (andes.utils.snapshot.load_ss) and skip all of that. Falls back to the replay path on ANDES version mismatch, a missing dill blob (the snapshot was saved without include_dill), or a load failure; the response's fallback_reason carries the explanation. Default false. |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json RestoreSnapshotResponse |
| 404 | Session or snapshot not found. | application/json ProblemDetails |
| 409 | No case loaded - restore needs a case to scope the snapshot directory. | application/json ProblemDetails |
| 422 | Invalid snapshot name OR corrupt metadata OR version mismatch on forced dill restore. | application/json ProblemDetails |
GET /api/sessions/{session_id}/snapshots¶
List snapshots saved against the current case.
List snapshots for the current case.
Empty list (200, {"snapshots": []}) when no case is loaded, no
snapshots have been saved, or the workspace lacks a snapshots
directory. The route does NOT 404 on "no case loaded" - the UI's
"Load snapshot…" menu wants to render an empty state cleanly.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json ListSnapshotsResponse |
| 404 | Session not found or already closed. | application/json ProblemDetails |
DELETE /api/sessions/{session_id}/snapshot/{name}¶
Delete a snapshot by name.
Delete snapshot endpoint.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes | |
name |
path | string | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
| 204 | Successful Response | |
| 404 | Session or snapshot not found. | application/json ProblemDetails |
| 409 | No case loaded - delete needs a case to scope the snapshot directory. | application/json ProblemDetails |
| 422 | Invalid snapshot name. | application/json ProblemDetails |
Bundles¶
POST /api/sessions/{session_id}/bundle/import¶
Import a reproducibility bundle (.zip) into the current session.
Import a reproducibility bundle.
See module docstring for the conflict-resolution flow. The
response is always a BundleImportResponse - the route
layer differentiates the two outcomes via HTTP status:
-
200 OK+status="committed": case loaded, disturbances replayed.case_filenameanddisturbances_replayedcarry the post-commit bookkeeping. -
409 Conflict+status="plan": conflicts present, nothing committed. The UI renders the conflict resolver and re-issues withforce_resolve=true.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Request body (required)
Media type multipart/form-data: Body_importBundle
| Field | Type | Required | Description |
|---|---|---|---|
file |
string | yes | Reproducibility bundle .zip (the same format produced by POST /api/sessions/{id}/bundle/export). |
force_resolve |
boolean | no | When False (default), the substrate validates the bundle and returns status=plan if any conflict is detected (sha mismatch, version mismatch, missing addfile). When True, the substrate proceeds with the extraction + replay honouring use_bundle_case / accept_version_mismatch. Default false. |
use_bundle_case |
boolean | no | Sha-mismatch resolution. True (default) overwrites the workspace's copy of the case file with the bundle's; False preserves the workspace file and writes the bundle's to a sibling <filename>.from-bundle for offline diff. Default true. |
accept_version_mismatch |
boolean | no | When True (default), the substrate proceeds even when the bundle's ANDES major.minor differs from the installed version (the warning surfaces in the response's plan). Default true. |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json BundleImportResponse |
| 400 | Bundle is not a valid ZIP archive. | application/json ProblemDetails |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 409 | Conflicts detected (sha mismatch / version mismatch / addfile missing). Body is a BundleImportResponse with status='plan' so the UI can render the conflict resolver. |
application/json ProblemDetails |
| 413 | Bundle exceeds the 67108864-byte cap. | application/json ProblemDetails |
| 422 | Manifest malformed (missing required fields, not a JSON object, references a case file the zip doesn't have, or names one with a file name Windows would misread such as CON.raw); OR disturbances.json malformed; OR caller forced resolution on a bundle with blocker conflicts; OR a case file cannot be written inside the workspace. |
application/json ProblemDetails |
Reports¶
GET /api/sessions/{session_id}/report¶
Render a human-readable report from PFlow or TDS results.
Produce a routine report and return {plain_text, structured}.
Routines: pflow, tds, eig.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes | |
routine |
query | ReportRoutineEnum | yes | Which routine to report on. pflow requires a converged power-flow result; tds requires a completed TDS run; eig requires EIG.run() to have populated the eigenvalue vector. |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json ReportResponse |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 409 | The routine pre-condition is not met - for pflow, no converged PFlow result; for tds, no completed TDS run; for eig, EIG has not been run yet on this session. |
application/json ProblemDetails |
| 422 | The requested routine is outside the schema enum. Validation-layer rejection. | application/json ProblemDetails |
| 500 | ANDES's report writer failed. The detail carries the underlying ANDES error verbatim. | application/json ProblemDetails |
Eigenvalue analysis¶
POST /api/sessions/{session_id}/eig¶
Run eigenvalue analysis (small-signal stability) on the session.
Synchronously run ss.EIG.run() and return the result.
Side-effect note: ANDES mutates dae.t to 0 and sets
TDS.initialized=True as part of EIG's pre-check. The response's
tds_initialized field surfaces this so the UI can render an
info banner ("Running EIG initialised the dynamic state...").
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Request body (required)
Media type application/json: EigRunRequest
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json EigResultResponse |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 409 | No case loaded OR the session has no converged PFlow result. Run /pflow first; EIG._pre_check only warns and would otherwise crash with a non-actionable TypeError. |
application/json ProblemDetails |
| 422 | ANDES eigenvalue routine raised (e.g., singular Jacobian after regularization). | application/json ProblemDetails |
GET /api/sessions/{session_id}/eig/modes/{mode_idx}/participation¶
Per-mode participation factor row for the given mode index.
Slice EIG.pfactors[mode_idx] and return the per-state row.
Substrate-side slice: there is no per-mode lazy API in ANDES. The
full pfactors matrix is held in memory after EIG.run.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes | |
mode_idx |
path | integer | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json EigParticipationResponse |
| 404 | Session not found, OR mode_idx is out of range for the current EIG result. |
application/json ProblemDetails |
| 409 | EIG has not been run on this session - POST /eig first. | application/json ProblemDetails |
GET /api/sessions/{session_id}/eig/state-matrix.mat¶
Download EIG.As + EIG.mu as a .mat file.
Return EIG.As (and EIG.mu) as a .mat blob.
Format is MATLAB v5 (compressed). The full matrix is shipped as a download because for non-trivial cases (NPCC 140 → 334×334) inline JSON would balloon the response unnecessarily.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | MATLAB v5 .mat file containing As (state matrix) and mu (eigenvalue vector). Consumed by the UI's MAT exporter. |
application/octet-stream |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 409 | EIG has not been run on this session - POST /eig first. | application/json ProblemDetails |
Continuation power flow¶
POST /api/sessions/{session_id}/cpf¶
Run continuation power flow (PV-curve / nose-curve) on the session.
Synchronously run ss.CPF.run() and return the trajectory.
Truncation (ok=False on the wrapper side) does NOT raise - the
response carries truncated=True and nose_idx=-1 so the UI
can show the "did not reach nose" note inline rather than as an
error banner.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Request body (required)
Media type application/json: CpfRunRequest
| Field | Type | Required | Description |
|---|---|---|---|
direction |
string | no | What lambda increases. 'load' (default) scales every load and every PV generator's output in proportion to its base value (CPF.run(load_scale=2.0)). 'load-only' scales the loads and leaves the PV generators where they are, so the slack generator supplies the increase. 'gen' scales the PV generators and leaves the loads. With these three, lambda = 1 is twice the base value. 'custom' moves the devices named in load_increase and generator_increase by the amounts given there, and lambda counts multiples of them. The slack generator is never part of a direction, and only PQ loads and PV generators move: ANDES's routine leaves every other load model as it is. One of load, load-only, gen, custom. Default "load". |
load_increase |
array of CpfLoadIncrease or null | no | For direction: 'custom': the MW and MVAr each named PQ load gains per unit of lambda. A load left out does not move. Limits: max items 100000. |
generator_increase |
array of CpfGeneratorIncrease or null | no | For direction: 'custom': the MW each named PV generator gains per unit of lambda. A generator left out does not move, and what the loads gain beyond the generators is supplied by the slack. Limits: max items 100000. |
enforce_q_limits |
boolean or null | no | Switch a PV or slack generator to a PQ bus held at qmin or qmax when its reactive output reaches one along the path. Left out, the case's own setting stands (off unless the case file turns pv2pq on). The continuation starts from the power flow as solved, so run that with enforce_q_limits as well: the request is refused with 409 when the solved power flow leaves a generator past a limit. Without it, the generators the power flow holds at a limit stay held and no other switches. A generator held at a limit stays held for the rest of the path (ANDES's limiter does not let go); would_release_step of its entry in limit_events says from where a real exciter would have. |
stop_at |
string | no | 'nose' (default) stops at the nose. 'full' turns there and follows the lower-voltage solutions back to lambda = 0 (CPF.config.stop_at = 'FULL'); the steps after nose_idx are that lower branch. One of nose, full. Default "nose". |
step |
number or null | no | Optional initial continuation step size for lambda (ss.CPF.config.step for this run). Default uses ANDES's own default (0.1). Limits: above 0. |
max_iter |
integer or null | no | Optional cap on the number of continuation steps (ss.CPF.config.max_steps for this run). This maps the user-facing parameter name onto ANDES's max_steps field, which actually controls truncation; ANDES's own max_iter config is the Newton corrector iterations per step. Default uses ANDES's own default (500). Limits: min 1. |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json CpfResultResponse |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 409 | No case loaded OR the session has no converged PFlow result. Run /pflow first; CPF.init only warns and would otherwise fall through to a non-actionable internal error. Also when enforce_q_limits is on and the solved power flow leaves a generator past a reactive limit: run /pflow with enforce_q_limits first. |
application/json ProblemDetails |
| 422 | The request cannot be run on this case (an increase names a device the case does not have, or the direction moves nothing), OR the ANDES CPF routine raised (e.g., singular Jacobian, KLU segfault, internal LinAlg failure). | application/json ProblemDetails |
POST /api/sessions/{session_id}/cpf/qv¶
Run a single-bus QV-curve continuation on the session.
Synchronously run ss.CPF.run_qv(bus_idx) and return the trace.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Request body (required)
Media type application/json: CpfQvRunRequest
| Field | Type | Required | Description |
|---|---|---|---|
bus_idx |
string | yes | Bus idx to draw the QV-curve at. Must match an entry in the loaded case's Bus.idx; ANDES requires at least one PQ device at this bus (raises ValueError otherwise, surfaced as 422 here). Limits: min length 1. |
q_range |
number or null | no | Reactive-power range for the QV continuation. Default matches ANDES's own q_range=5.0. Limits: above 0. |
enforce_q_limits |
boolean or null | no | Hold generators to their reactive limits along the curve, as enforce_q_limits of POST /sessions/{id}/cpf does. |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json CpfResultResponse |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 409 | No case loaded OR the session has no converged PFlow result. Run /pflow first. | application/json ProblemDetails |
| 422 | ANDES CPF.run_qv raised - typically because no PQ device is attached to bus_idx or the case is too stiff for the QV continuation. |
application/json ProblemDetails |
State estimation¶
POST /api/sessions/{session_id}/se/measurements/generate¶
Generate the default SE measurement set from the converged PF solution.
Build the default measurement set and cache it on the worker.
Default set mirrors ANDES's own SE._default_measurements:
add_bus_voltage(sigma=0.01) + add_bus_injection(sigma_p=0.02,
sigma_q=0.03). The substrate caches the populated Measurements
object so a subsequent /se call doesn't have to regenerate
noise (lets the user re-run SE against a stable measurement set).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Request body (required)
Media type application/json: SeGenerateMeasurementsRequest
| Field | Type | Required | Description |
|---|---|---|---|
noise_seed |
integer or null | no | Optional non-negative integer seed for the Gaussian-noise draw inside Measurements.generate_from_pflow. None (default) uses an unseeded np.random.default_rng; pinning the seed lets the UI re-generate the same measurement set across page refreshes. ge=0 because numpy's default_rng rejects negative seeds - a clean 422 here beats a misleading non-convergent error downstream. Limits: min 0. |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json SeMeasurementsGeneratedResponse |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 409 | No case loaded OR the session has no converged PFlow result. Run /pflow first; SE.init only logs an error and would otherwise return False with no actionable detail. |
application/json ProblemDetails |
| 422 | Measurement generation failed (e.g., a model lookup raised inside add_bus_injection). |
application/json ProblemDetails |
POST /api/sessions/{session_id}/se¶
Run static state estimation against the cached measurement set.
Synchronously run ss.SE.run() against the cached measurement
set and return the result.
Pre-conditions enforced by the substrate (mapped to 409):
-
ss.PFlow.converged is True- independent gate (ANDES warns but doesn't short-circuit). -
The substrate has a cached
Measurementsobject from a prior call to/se/measurements/generate.
Failure modes mapped to 422:
- Under-determined measurement set (gain matrix singular).
- WLS non-convergent within
config.max_iter.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Request body (required)
Media type application/json: SeRunRequest
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json SeResultResponse |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 409 | No case loaded OR the session has no converged PFlow result OR the substrate has no cached measurement set yet (call /se/measurements/generate first). | application/json ProblemDetails |
| 422 | Either: (a) the measurement set is under-determined (insufficient redundancy; gain matrix singular), or (b) the WLS Gauss-Newton did not converge within config.max_iter. |
application/json ProblemDetails |
PMU placement¶
GET /api/sessions/{session_id}/pmu¶
List currently-placed PMU instances on the session.
Return every PMU currently registered on the session's System.
Empty list when none have been placed (the common case for a freshly-loaded stock case - ANDES's bundled cases ship with zero PMUs).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json ListPmusResponse |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 409 | No case has been loaded into this session yet. | application/json ProblemDetails |
POST /api/sessions/{session_id}/pmu¶
Place a PMU at the given bus on a pre-setup session.
Add a PMU instance to the session pre-setup.
Returns the freshly-built TopologyEntry with the auto-assigned
ANDES idx (PMU_<n>). The placement dialog seeds its local list
from the response so the new PMU shows up without an extra GET.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Request body (required)
Media type application/json: AddPmuRequest
| Field | Type | Required | Description |
|---|---|---|---|
bus_idx |
string | yes | Bus idx the PMU attaches to. Must reference a Bus that exists on the loaded System. Integer-typed bus idxes (from PSS/E .raw cases) are accepted as their string representation. Limits: min length 1. |
Ta |
number or null | no | Angle filter time constant in seconds. Defaults to 0.05 s when omitted (the substrate's recommended sweet spot). Limits: above 0. |
Tv |
number or null | no | Voltage filter time constant in seconds. Defaults to 0.05 s when omitted. Limits: above 0. |
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Successful Response | application/json TopologyEntry |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 409 | Session has already been committed (PF or TDS has run); call POST /api/sessions/{id}/reload to return to pre-setup. | application/json ProblemDetails |
| 422 | Bus idx does not exist on the loaded System OR ANDES rejected the underlying ss.add('PMU', ...) call. |
application/json ProblemDetails |
DELETE /api/sessions/{session_id}/pmu/{pmu_idx}¶
Remove a PMU from the pre-setup session.
Delete a PMU, whether POST /pmu placed it or the case file brought it.
Returns 204 on success. 404 when the idx isn't a known PMU. 409 when the session is post-setup (call /reload first).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes | |
pmu_idx |
path | string | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
| 204 | Successful Response | |
| 404 | Session not found OR no PMU with that idx. | application/json ProblemDetails |
| 409 | Session has already been committed; call POST /api/sessions/{id}/reload to return to pre-setup. | application/json ProblemDetails |
| 422 | A disturbance acts on the PMU (a toggle of it), and would be left naming a device that is gone. | application/json ProblemDetails |
GET /api/sessions/{session_id}/pmu/{run_id}/export.csv¶
Download the PMU am/vm trajectories from the most recent TDS run.
Stream the PMU CSV for the session's most recent TDS run.
The run_id segment is informational on the substrate side (the
substrate keeps only the latest ss.dae.ts); the client uses it
so the downloaded file can be stably named per-run.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes | |
run_id |
path | string | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | CSV body. Header is t,<idx1>_am,<idx1>_vm,...; one row per integration step at full TDS rate (no decimation). Empty body (header only) when no PMUs are placed or no TDS step has fired. |
text/csv |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 409 | No case has been loaded OR the session has not yet committed setup() (i.e., no PF or TDS run has fired). | application/json ProblemDetails |
Profiles¶
POST /api/sessions/{session_id}/profiles/upload¶
Upload a CSV / XLSX hourly profile to the session's workspace.
Upload a profile and persist it to <workspace>/profiles/<uuid>.xlsx.
The follow-up POST /profiles references the returned
profile_path to stage the TimeSeries device.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Request body (required)
Media type multipart/form-data: Body_uploadProfile
| Field | Type | Required | Description |
|---|---|---|---|
file |
string | yes | CSV or XLSX file. CSV inputs are transcoded to xlsx (single sheet named profile) for uniformity with ANDES's preferred input format. |
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Successful Response | application/json UploadProfileResponse |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 409 | The substrate was launched without a workspace - profile uploads require disk persistence. | application/json ProblemDetails |
| 413 | Upload exceeds the 8388608-byte cap; split the profile into a smaller window. | application/json ProblemDetails |
| 422 | Unsupported file extension OR malformed CSV (parse failure / non-UTF-8 bytes). | application/json ProblemDetails |
| 500 | Disk write failed (full filesystem, permissions, or openpyxl error). The error detail surfaces the underlying cause. | application/json ProblemDetails |
GET /api/sessions/{session_id}/profiles¶
List currently-staged TimeSeries profiles on the session.
Return every TimeSeries device currently registered on the session's System.
Empty list when none have been staged (the common case for a freshly-loaded stock case - ANDES's bundled cases ship with zero TimeSeries devices).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json ListProfilesResponse |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 409 | No case has been loaded into this session yet. | application/json ProblemDetails |
POST /api/sessions/{session_id}/profiles¶
Stage a TimeSeries profile assignment on a pre-setup session.
Stage a TimeSeries device pre-setup.
Returns the freshly-built TopologyEntry with the auto-assigned
ANDES idx (TimeSeries_<n>).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Request body (required)
Media type application/json: AddProfileRequest
| Field | Type | Required | Description |
|---|---|---|---|
profile_path |
string | yes | Absolute path of the on-disk profile xlsx - the value returned by POST /profiles/upload. The file must exist before this call (ANDES reads it during setup). Limits: min length 1. |
sheet |
string | yes | XLSX sheet name to read. CSV uploads are transcoded to a single sheet named profile; XLSX uploads keep their original sheet names. Limits: min length 1. |
fields |
string | yes | Comma-separated column names from the source sheet providing the time series of values (e.g., p0, p0,q0). Each entry must match an existing column in the sheet. Limits: min length 1. |
model |
string | yes | ANDES model class name of the target device (e.g., PQ, PV, Slack). Must reference a model present on the loaded System. Limits: min length 1. |
dev |
string | yes | Idx of the target device within model (e.g., PQ_5). The substrate validates existence before the underlying ss.add so the user gets a clean 422 instead of an ANDES-internal error. Limits: min length 1. |
dests |
string | yes | Comma-separated parameter names on the target device that receive the profile values (e.g., p0, p0,q0). Must align with fields (one destination per source column). Limits: min length 1. |
tkey |
string | no | Source column carrying the timestamp (in seconds). Defaults to t - the convention shipped by ANDES's bundled examples. Limits: min length 1. Default "t". |
mode |
integer | no | Application mode. 1 (exact) applies values at exact step times. 2 (interpolated) raises NotImplementedError in ANDES, so the substrate rejects mode=2 with 422; default to mode=1. Limits: min 1, max 2. Default 1. |
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Successful Response | application/json TopologyEntry |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 409 | Session has already been committed (PF or TDS has run); call POST /api/sessions/{id}/reload to return to pre-setup. | application/json ProblemDetails |
| 422 | Profile file does not exist OR target model/device is absent OR mode=2 was requested OR ANDES rejected the underlying ss.add('TimeSeries', ...) call. |
application/json ProblemDetails |
DELETE /api/sessions/{session_id}/profiles/{profile_idx}¶
Remove a staged TimeSeries from the pre-setup session.
Delete a TimeSeries, whether POST /profiles staged it or the case
file brought it.
Returns 204 on success. 404 when the idx isn't a known TimeSeries. 409 when the session is post-setup (call /reload first).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes | |
profile_idx |
path | string | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
| 204 | Successful Response | |
| 404 | Session not found OR no TimeSeries with that idx. | application/json ProblemDetails |
| 409 | Session has already been committed; call POST /api/sessions/{id}/reload to return to pre-setup. | application/json ProblemDetails |
| 422 | A disturbance acts on the TimeSeries (a toggle of it), and would be left naming a device that is gone. | application/json ProblemDetails |
Sweeps¶
POST /api/sessions/{session_id}/sweep¶
Start a sensitivity sweep.
Start a sweep. Returns immediately; the WS channel emits progress.
Validates the parameter kind locally (so a bad request doesn't
waste a worker round trip), materialises the parameter range, then
schedules the background task via SessionManager.start_sweep.
The sweep_in_progress flag is set BEFORE this call returns so
the next session-scoped route call observes it.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Request body (required)
Media type application/json: SweepRequest
| Field | Type | Required | Description |
|---|---|---|---|
parameter |
SweepParameter | yes | Parameter target + range definition. Identifies the spec to mutate per iteration and the values to iterate over. |
sim |
SweepSimParams | yes | Per-iteration TDS simulation parameters (tf, optional h, optional vars). Applied identically to every iteration. |
snapshot_name |
string | yes | Name of a previously-saved snapshot. Each iteration restores this snapshot before applying the parameter override. |
Responses
| Status | Meaning | Body |
|---|---|---|
| 202 | Sweep started; subscribe to the WS channel for progress. | application/json StartSweepResponse |
| 404 | Session not found or already closed. | application/json ProblemDetails |
| 409 | A sweep is already running on this session OR no snapshot was found by the requested name. | application/json ProblemDetails |
| 422 | Invalid sweep parameter kind, target out of range, or snapshot has no matching disturbance. | application/json ProblemDetails |
Jobs¶
GET /api/sessions/{session_id}/jobs¶
List jobs for a session.
Return the session's jobs, newest-last, optionally filtered.
kind filters by routine kind; status (the wire query param) filters
by lifecycle state - aliased to the local status_filter so it doesn't
shadow the imported status module. Both default to None (no
filter). The list spans the per-session registry AND the manager-wide
global registry so session-mutating jobs surface here too.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes | |
kind |
query | string or null | no | One of pflow, tds-batch, tds-stream, eig, cpf, cpf-qv, se, se-measurements, sweep, snapshot-save, snapshot-restore, snapshot-delete, bundle-export, bundle-import, case-load, case-reload, case-save, element-add, element-edit, element-delete, element-undo, element-redo, disturbance-commit, pmu-add, pmu-delete, profile-upload, profile-add, profile-delete, clone-init, clone-edit, clone-undo, clone-redo, clone-save-as, clone-reset. |
status |
query | string or null | no | Optional lifecycle-state filter (pending/running/done/failed/cancelled). One of pending, running, done, failed, cancelled. |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json array of JobRecordSchema |
| 404 | Session not found or already closed. | application/json ProblemDetails |
GET /api/sessions/{session_id}/jobs/{job_id}¶
Fetch one job by id.
Return one job record. 404 when the session or job id is unknown.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes | |
job_id |
path | string | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json JobRecordSchema |
| 404 | Session or job not found. | application/json ProblemDetails |
DELETE /api/sessions/{session_id}/jobs/{job_id}¶
Cancel a job.
Cancel a job.
-
can_cancel == true→mark_cancelled+ broadcast + 200 with the updated record. -
can_cancel == false→ 409SessionBusyError(wait-for-jobrecovery,retryable: false): the job is running and must be waited out. The sharedSessionBusyErrorexception handler renders the ProblemDetails envelope with the recovery CTA + thecurrent_job. -
Unknown job id → 404.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes | |
job_id |
path | string | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Job cancelled; the updated record is returned. | application/json JobRecordSchema |
| 404 | Session or job not found. | application/json ProblemDetails |
| 409 | The job is not cancellable (a synchronous routine that must run to completion). Carries a wait-for-job recovery CTA. |
application/json ProblemDetails |
ANDES messages¶
GET /api/sessions/{session_id}/messages¶
Read what ANDES said while the session's commands ran.
The messages ANDES logged for this session, oldest first, at most
limit of them. Warnings and errors are what to look for after a run that
gave an odd result; info adds how each run went (iteration counts, the
events a time-domain run applied). A read costs the worker nothing and does
not wait for a command in progress.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes | |
after |
query | integer | no | Only messages numbered above this. Pass the next_after of the previous read to get what came since; 0 reads from the start. Limits: min 0. Default 0. |
level |
query | string | no | The lowest level to return: warning gives warnings and errors, error only errors. One of info, warning, error. Default "info". |
limit |
query | integer | no | Most messages to return. Limits: min 1, max 2000. Default 500. |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | Successful Response | application/json SessionMessages |
| 404 | Session not found or already closed. | application/json ProblemDetails |
DELETE /api/sessions/{session_id}/messages¶
Forget the session's messages.
Empty the session's message log. Message numbers go on from where they were, so a client that reads on from the last number it has sees only what is logged from now.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
| 204 | Successful Response | |
| 404 | Session not found or already closed. | application/json ProblemDetails |