6.6 KiB
Circuit List Editor API
Scope
The circuit-first editor uses tree, circuit, row and project-device endpoints.
All paths below are mounted below /api. There is no Consumer application API.
Project responses from GET /projects and GET /projects/:projectId include
currentRevision. Versioned project commands use this value for optimistic
concurrency checks.
Project Commands and History
GET /projects/:projectId/history- returns
currentRevision,undoDepth,redoDepthand the current top change-set id for both persistent stacks
- returns
POST /projects/:projectId/commands- executes a supported versioned command as a new user revision
- body:
{ "expectedRevision": 0, "command": { ... } }
POST /projects/:projectId/history/undoPOST /projects/:projectId/history/redo- body:
{ "expectedRevision": 1 } - executes the eligible inverse or forward command as a new auditable revision
- body:
The public dispatcher currently supports circuit.update, circuit.insert,
circuit.delete, circuit-device-row.update,
circuit-device-row.insert, circuit-device-row.delete and
circuit-device-row.move, all with schema version 1. Other command types are
rejected. Insert commands contain the complete entity or circuit block with
stable ids; delete commands include the expected parent identity. Circuit
snapshots contain zero, one or multiple complete device rows. Move commands
contain each row's expected and target circuit plus its exact expected and
target sort order. This makes deletion and moves undoable without generating
replacement identities or renumbering circuits. The editor does not consume
these endpoints yet.
circuit-device-row.move currently targets existing circuits in the same
circuit list. Creating a new target circuit while moving rows is not yet part
of the persistent command API.
Example:
{
"expectedRevision": 0,
"description": "Rename circuit",
"command": {
"schemaVersion": 1,
"type": "circuit.update",
"payload": {
"circuitId": "cir_1",
"changes": [
{ "field": "displayName", "value": "Sockets East" }
]
}
}
}
A stale expectedRevision returns HTTP 409 with
PROJECT_REVISION_CONFLICT. Undo or redo without an eligible stack entry
returns HTTP 409 with PROJECT_HISTORY_OPERATION_UNAVAILABLE.
Circuit-First Endpoints
Distribution Board Setup
POST /projects/:projectId/distribution-boards- atomically creates the distribution board, its circuit list and all default circuit sections
Tree Endpoint
GET /projects/:projectId/circuit-lists/:circuitListId/tree- Purpose: returns section/circuit/device-row tree with calculated row and circuit totals.
Response sketch:
{
"circuitListId": "cl_1",
"sections": [
{
"id": "sec_1",
"key": "lighting",
"prefix": "-1F",
"circuits": [
{
"id": "cir_1",
"equipmentIdentifier": "-1F1",
"circuitTotalPower": 4.2,
"deviceRows": [
{
"id": "row_1",
"displayName": "Office lights",
"rowTotalPower": 1.8
}
]
}
]
}
]
}
Circuit CRUD
POST /projects/:projectId/circuit-lists/:circuitListId/circuits- create circuit in list/section
POST /projects/:projectId/circuit-lists/:circuitListId/circuits-with-device-rows- atomically create one circuit and one or more initial device rows
PATCH /circuits/:circuitId- update circuit-level fields (BMK, protection, cable, reserve flag, etc.)
DELETE /circuits/:circuitId- delete circuit (and related rows via DB relations)
GET /circuit-sections/:sectionId/next-identifier- preview next identifier for section (
prefix + maxSuffix + 1)
- preview next identifier for section (
Request sketch (POST .../circuits):
{
"sectionId": "sec_1",
"equipmentIdentifier": "-2F14",
"displayName": "Sockets East",
"sortOrder": 140
}
Device Row CRUD
POST /circuits/:circuitId/device-rows- create row inside a circuit
PATCH /circuit-device-rows/:rowId- update row values
DELETE /circuit-device-rows/:rowId- delete row
Move Device Row
PATCH /circuit-device-rows/:rowId/move- Purpose: move one row to existing circuit or to newly created circuit in target section.
Request sketch:
{
"targetCircuitId": "cir_target"
}
or
{
"targetSectionId": "sec_target",
"createNewCircuit": true
}
Bulk Move Device Rows
PATCH /circuit-device-rows/move-bulk- Purpose: move multiple rows in one command flow.
Request sketch:
{
"rowIds": ["row_1", "row_2"],
"targetCircuitId": "cir_target"
}
Reorder Circuits
PATCH /circuit-sections/:sectionId/circuits/reorder- Purpose: persist explicit circuit order within one section.
Request sketch:
{
"orderedCircuitIds": ["cir_2", "cir_1", "cir_3"]
}
Renumber Section
POST /circuit-sections/:sectionId/renumber- Purpose: explicit renumbering by section prefix; never implicit on move/sort.
Safe Equipment Identifier Update
PATCH /circuit-sections/:sectionId/equipment-identifiers- Purpose: apply explicit per-circuit identifiers safely even with unique constraints.
Request sketch:
{
"identifiers": [
{ "circuitId": "cir_1", "equipmentIdentifier": "-2F1" },
{ "circuitId": "cir_2", "equipmentIdentifier": "-2F2" }
]
}
Removed Legacy Endpoints
The former /consumers read/write endpoints were removed after every retained
consumer had a verified Circuit-First migration mapping. Database upgrade tooling
reads retained legacy rows directly; application features must use the Circuit-First
endpoints above.
Linked Project Device Review
GET /project-devices/projects/:projectId/:projectDeviceId/links- returns all linked circuit device rows with distribution-board/circuit context and field differences
POST /project-devices/projects/:projectId/:projectDeviceId/synchronize- applies only explicitly selected fields to explicitly selected linked rows
POST /project-devices/projects/:projectId/:projectDeviceId/restore- restores the captured pre-sync values for session-local undo
POST /project-devices/projects/:projectId/:projectDeviceId/disconnect- disconnects explicitly selected rows without changing their local values
POST /project-devices/projects/:projectId/:projectDeviceId/reconnect- restores a disconnected link if the row has not been linked elsewhere meanwhile
displayName is included in the comparison but is not selected by default in the UI. Updating a
project device never triggers synchronization implicitly.