Skip to content

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.

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_filename and disturbances_replayed carry the post-commit bookkeeping.

  • 409 Conflict + status="plan": conflicts present, nothing committed. The UI renders the conflict resolver and re-issues with force_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 Measurements object 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 → 409 SessionBusyError (wait-for-job recovery, retryable: false): the job is running and must be waited out. The shared SessionBusyError exception handler renders the ProblemDetails envelope with the recovery CTA + the current_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