GraphQL API
EnerOS provides a GraphQL interface via the eneros-graphql crate, with the endpoint POST /graphql. GraphQL is suitable for scenarios that require flexible field selection and multi-type aggregate queries, avoiding the over-fetching / under-fetching problems of REST. EnerOS’s GraphQL implementation is based on async-graphql, fully asynchronous and zero-copy, sharing the same set of system calls with the kernel.
Endpoint Information
| Item | Value |
|---|---|
| URL | http://localhost:8080/graphql |
| Method | POST |
| Content-Type | application/json |
| Authentication | Bearer Token (Header) or mTLS |
| Subscription Endpoint | ws://localhost:8080/graphql (WebSocket Subscriptions protocol) |
| Introspection | Enabled in development, disabled by default in production |
| Query Depth Limit | 7 levels |
| Complexity Limit | 1000 |
| Query Timeout | 30 seconds |
| Alias Limit Per Request | 20 |
Request Format
The GraphQL request body is uniformly JSON, containing the following fields:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
query | string | Yes | — | GraphQL query string |
variables | object | No | null | Variable dictionary |
operationName | string | No | — | Specified when query contains multiple operations |
extensions | object | No | — | Extension fields (e.g., persisted queries) |
Complete Schema
Scalar Types
In addition to standard scalars (String/Int/Float/Boolean/ID), EnerOS defines the following custom scalars:
scalar DateTime # RFC 3339 time string
scalar JSON # Any JSON value
scalar Float64 # IEEE 754 double
scalar UInt64 # 64-bit unsigned integer
scalar Decimal # High-precision decimal (for electrical quantity calculations)
Enum Types
enum NetworkStatus {
DRAFT
READY
LOCKED
ARCHIVED
}
enum AgentStatus {
IDLE
RUNNING
PAUSED
ERROR
STOPPED
}
enum AgentType {
DISPATCH
SELF_HEALING
TRADING
MAINTENANCE
PLANNING
}
enum BusType {
SLACK
PV
PQ
}
enum DeviceType {
PV
WIND
BESS
EV
LOAD
TRANSFORMER
BREAKER
}
enum PowerFlowMethod {
NEWTON_RAPHSON
FAST_DECOUPLED
DC
}
enum PowerFlowStatus {
QUEUED
RUNNING
CONVERGED
DIVERGED
TIMEOUT
CANCELLED
}
enum Aggregation {
NONE
AVG
SUM
MAX
MIN
COUNT
PERCENTILE_50
PERCENTILE_95
PERCENTILE_99
}
enum Severity {
INFO
WARN
ERROR
CRITICAL
}
enum AlarmStatus {
ACTIVE
ACKNOWLEDGED
CLEARED
}
enum CommandPriority {
LOW
NORMAL
HIGH
EMERGENCY
}
enum BranchStatus {
CLOSED
OPEN
MAINTENANCE
}
Input Types
input NetworkFilter {
status: NetworkStatus
nameContains: String
tags: [TagFilterInput!]
createdAfter: DateTime
createdBefore: DateTime
}
input TagFilterInput {
key: String!
value: String
}
input NetworkInput {
name: String!
description: String
baseKv: Float
baseMva: Float
buses: [BusInput!]!
branches: [BranchInput!]
generators: [GeneratorInput!]
loads: [LoadInput!]
tags: JSON
}
input BusInput {
id: Int!
type: BusType!
v: Float
theta: Float
area: Int
}
input BranchInput {
from: Int!
to: Int!
r: Float!
x: Float!
b: Float
status: BranchStatus
}
input GeneratorInput {
bus: Int!
mw: Float!
mvar: Float
pmin: Float
pmax: Float
qmin: Float
qmax: Float
}
input LoadInput {
bus: Int!
mw: Float!
mvar: Float
}
input AgentInput {
name: String!
type: AgentType!
networkId: ID
config: JSON
tools: [String!]
reasoningModel: String
maxActionsPerMinute: Int
autoStart: Boolean
}
input CommandInput {
command: String!
params: JSON
priority: CommandPriority
timeoutMs: Int
awaitResult: Boolean
}
input TimeSeriesQuery {
metric: String!
tags: [TagFilterInput!]
from: DateTime!
to: DateTime!
aggregation: Aggregation
interval: String
fill: String
limit: Int
}
Object Types
type Network {
id: ID!
name: String!
description: String
baseKv: Float!
baseMva: Float!
busCount: Int!
branchCount: Int!
generatorCount: Int!
loadCount: Int!
status: NetworkStatus!
tags: JSON
buses: [Bus!]!
branches: [Branch!]!
generators: [Generator!]!
loads: [Load!]!
createdAt: DateTime!
updatedAt: DateTime!
powerFlow(method: PowerFlowMethod): PowerFlowResult
agents: [Agent!]!
alarms(severity: Severity): [Alarm!]!
}
type Bus {
id: Int!
type: BusType!
voltage: Voltage!
area: Int
generators: [Generator!]!
loads: [Load!]!
}
type Voltage {
magnitude: Float!
angle: Float!
}
type Branch {
from: Int!
to: Int!
r: Float!
x: Float!
b: Float!
status: BranchStatus!
flow: BranchFlow
}
type BranchFlow {
pFrom: Float!
qFrom: Float!
pTo: Float!
qTo: Float!
loading: Float!
}
type Generator {
bus: Int!
mw: Float!
mvar: Float!
pmin: Float
pmax: Float
qmin: Float
qmax: Float
}
type Load {
bus: Int!
mw: Float!
mvar: Float!
}
type Agent {
id: ID!
name: String!
type: AgentType!
networkId: ID
network: Network
status: AgentStatus!
config: JSON
tools: [String!]!
reasoningModel: String!
maxActionsPerMinute: Int!
createdAt: DateTime!
startedAt: DateTime
lastActiveAt: DateTime
actions: [AgentAction!]!
}
type AgentAction {
id: ID!
agentId: ID!
command: String!
params: JSON
status: String!
result: JSON
executedAt: DateTime!
durationMs: Int!
}
type PowerFlowResult {
taskId: ID!
networkId: ID!
status: PowerFlowStatus!
method: PowerFlowMethod!
iterations: Int
durationMs: Int!
tolerance: Float!
buses: [BusResult!]!
branches: [BranchResult!]!
losses: Losses
startedAt: DateTime!
completedAt: DateTime
}
type BusResult {
id: Int!
v: Float!
theta: Float!
pGen: Float
qGen: Float
pLoad: Float
qLoad: Float
}
type BranchResult {
from: Int!
to: Int!
pFrom: Float!
qFrom: Float!
pTo: Float!
qTo: Float!
loading: Float!
}
type Losses {
pMw: Float!
qMvar: Float!
}
type Device {
id: ID!
name: String!
type: DeviceType!
status: String!
networkId: ID
bus: Int
capacityMw: Float
online: Boolean!
metadata: JSON
lastSeenAt: DateTime
}
type TimeSeriesPoint {
timestamp: DateTime!
value: Float!
tags: JSON
}
type Alarm {
id: ID!
severity: Severity!
status: AlarmStatus!
type: String!
message: String!
networkId: ID
source: AlarmSource!
details: JSON
raisedAt: DateTime!
acknowledgedAt: DateTime
acknowledgedBy: String
clearedAt: DateTime
}
type AlarmSource {
type: String!
id: ID!
}
type CommandResult {
commandId: ID!
agentId: ID!
command: String!
status: String!
result: JSON
durationMs: Int!
executedAt: DateTime!
}
Query Type
type Query {
# Network
networks(filter: NetworkFilter, page: Int, pageSize: Int): NetworkConnection!
network(id: ID!): Network
# Agent
agents(status: AgentStatus, type: AgentType, page: Int, pageSize: Int): AgentConnection!
agent(id: ID!): Agent
# Device
devices(type: DeviceType, online: Boolean, page: Int, pageSize: Int): DeviceConnection!
device(id: ID!): Device
# Time Series
timeseries(query: TimeSeriesQuery!): [TimeSeriesPoint!]!
timeseriesMulti(queries: [TimeSeriesQuery!]!): [[TimeSeriesPoint!]!]!
# Events and Alarms
events(topic: String, from: DateTime, to: DateTime, page: Int, pageSize: Int): EventConnection!
alarms(severity: Severity, status: AlarmStatus, from: DateTime, to: DateTime): [Alarm!]!
alarm(id: ID!): Alarm
# Audit
audit(actor: String, action: String, from: DateTime, to: DateTime, page: Int, pageSize: Int): AuditConnection!
# Health
health: HealthStatus!
}
type NetworkConnection {
items: [Network!]!
total: Int!
page: Int!
pageSize: Int!
}
type AgentConnection {
items: [Agent!]!
total: Int!
page: Int!
pageSize: Int!
}
type DeviceConnection {
items: [Device!]!
total: Int!
page: Int!
pageSize: Int!
}
type EventConnection {
items: [Event!]!
total: Int!
page: Int!
pageSize: Int!
}
type AuditConnection {
items: [AuditEntry!]!
total: Int!
page: Int!
pageSize: Int!
}
type Event {
id: ID!
topic: String!
kind: String!
payload: JSON!
timestamp: DateTime!
}
type AuditEntry {
id: ID!
timestamp: DateTime!
actorType: String!
actorId: String!
tenant: String!
action: String!
resourceType: String!
resourceId: ID!
requestId: ID!
ip: String
userAgent: String
result: String!
details: JSON
}
type HealthStatus {
status: String!
version: String!
uptimeSeconds: Int!
checks: JSON!
}
Mutation Type
type Mutation {
# Network
createNetwork(input: NetworkInput!): Network!
updateNetwork(id: ID!, input: NetworkInput!): Network!
deleteNetwork(id: ID!): Boolean!
validateNetwork(id: ID!): ValidationResult!
# Power Flow
runPowerFlow(id: ID!, method: PowerFlowMethod, async: Boolean): PowerFlowResult!
cancelTask(taskId: ID!): Boolean!
# Agent
createAgent(input: AgentInput!): Agent!
updateAgent(id: ID!, input: AgentInput!): Agent!
deleteAgent(id: ID!): Boolean!
pauseAgent(id: ID!): Agent!
resumeAgent(id: ID!): Agent!
sendCommand(agentId: ID!, command: CommandInput!): CommandResult!
# Alarm
acknowledgeAlarm(id: ID!, note: String): Alarm!
# Time Series Write
writeTimeseries(points: [TimeSeriesPointInput!]!): WriteResult!
}
type ValidationResult {
valid: Boolean!
errors: [ValidationError!]
}
type ValidationError {
field: String!
message: String!
code: String!
}
input TimeSeriesPointInput {
metric: String!
timestamp: DateTime
value: Float!
tags: JSON
}
type WriteResult {
accepted: Int!
rejected: Int!
errors: [WriteError!]
}
type WriteError {
index: Int!
message: String!
}
Subscription Type
type Subscription {
networkChanged(id: ID): NetworkEvent!
agentStatusChanged(id: ID): AgentStatusEvent!
alarmStream(severity: Severity): Alarm!
eventStream(topic: String): Event!
powerFlowProgress(taskId: ID!): PowerFlowProgress!
}
type NetworkEvent {
networkId: ID!
kind: String!
changes: JSON!
timestamp: DateTime!
}
type AgentStatusEvent {
agentId: ID!
oldStatus: AgentStatus!
newStatus: AgentStatus!
reason: String
timestamp: DateTime!
}
type PowerFlowProgress {
taskId: ID!
iteration: Int!
maxIterations: Int!
mismatch: Float!
timestamp: DateTime!
}
Query Examples
Query Network with Power Flow Results
query GetNetworkWithPowerFlow($id: ID!) {
network(id: $id) {
id
name
baseKv
status
buses {
id
type
voltage { magnitude angle }
}
branches { from to status }
powerFlow(method: NEWTON_RAPHSON) {
status
iterations
durationMs
buses { id v theta }
branches { from to loading }
losses { pMw qMvar }
}
}
}
Variables:
{
"id": "net_8f3a2b"
}
Response:
{
"data": {
"network": {
"id": "net_8f3a2b",
"name": "IEEE-14",
"baseKv": 110.0,
"status": "READY",
"buses": [
{"id": 1, "type": "SLACK", "voltage": {"magnitude": 1.05, "angle": 0.0}},
{"id": 2, "type": "PV", "voltage": {"magnitude": 1.04, "angle": -0.039}}
],
"branches": [{"from": 1, "to": 2, "status": "CLOSED"}],
"powerFlow": {
"status": "CONVERGED",
"iterations": 4,
"durationMs": 12,
"buses": [
{"id": 1, "v": 1.05, "theta": 0.0},
{"id": 2, "v": 1.04, "theta": -0.039}
],
"branches": [{"from": 1, "to": 2, "loading": 0.45}],
"losses": {"pMw": 1.23, "qMvar": 5.67}
}
}
}
}
Time Series Aggregation Query
query GetTimeSeries($query: TimeSeriesQuery!) {
timeseries(query: $query) {
timestamp
value
tags
}
}
Variables:
{
"query": {
"metric": "branch_loading",
"tags": [{"key": "network", "value": "net_8f3a2b"}, {"key": "branch", "value": "1-2"}],
"from": "2026-07-06T00:00:00Z",
"to": "2026-07-06T08:00:00Z",
"aggregation": "AVG",
"interval": "5m"
}
}
Multi Time Series Parallel Query
query GetMultiTimeSeries($queries: [TimeSeriesQuery!]!) {
timeseriesMulti(queries: $queries) {
timestamp
value
}
}
Variables:
{
"queries": [
{"metric": "branch_loading", "from": "2026-07-06T00:00:00Z", "to": "2026-07-06T08:00:00Z", "aggregation": "AVG", "interval": "5m"},
{"metric": "bus_voltage", "from": "2026-07-06T00:00:00Z", "to": "2026-07-06T08:00:00Z", "aggregation": "AVG", "interval": "5m"}
]
}
Using Fragments
fragment NetworkBasic on Network {
id
name
status
busCount
createdAt
}
query GetNetworks {
networks(filter: {status: READY}) {
items {
...NetworkBasic
}
total
}
}
Using Aliases
query GetMultipleNetworks {
net1: network(id: "net_8f3a2b") {
id
name
status
}
net2: network(id: "net_9c4d1e") {
id
name
status
}
}
Mutation Examples
Create Network
mutation CreateNetwork($input: NetworkInput!) {
createNetwork(input: $input) {
id
name
status
busCount
branchCount
createdAt
}
}
Variables:
{
"input": {
"name": "IEEE-14",
"baseKv": 110.0,
"baseMva": 100.0,
"buses": [
{"id": 1, "type": "SLACK", "v": 1.05, "theta": 0.0},
{"id": 2, "type": "PV", "v": 1.04},
{"id": 3, "type": "PQ"}
],
"branches": [
{"from": 1, "to": 2, "r": 0.01938, "x": 0.05917, "b": 0.0528}
],
"tags": {"owner": "ieee"}
}
}
Trigger Power Flow Calculation
mutation RunPowerFlow($id: ID!, $method: PowerFlowMethod) {
runPowerFlow(id: $id, method: $method, async: false) {
taskId
status
iterations
durationMs
buses { id v theta }
branches { from to loading }
losses { pMw qMvar }
}
}
Create Agent and Send Command
mutation CreateAgentAndCommand($agentInput: AgentInput!, $commandInput: CommandInput!) {
createAgent(input: $agentInput) {
id
name
status
}
}
mutation SendCommand($agentId: ID!, $command: CommandInput!) {
sendCommand(agentId: $agentId, command: $command) {
commandId
status
result
durationMs
}
}
Write Time Series Data
mutation WriteTimeseries($points: [TimeSeriesPointInput!]!) {
writeTimeseries(points: $points) {
accepted
rejected
errors { index message }
}
}
Variables:
{
"points": [
{"metric": "branch_loading", "value": 0.42, "tags": {"network": "net_8f3a2b", "branch": "1-2"}},
{"metric": "bus_voltage", "value": 1.05, "tags": {"network": "net_8f3a2b", "bus": "1"}}
]
}
Subscription Examples
Subscribe to Alarm Stream
subscription AlarmStream($severity: Severity) {
alarmStream(severity: $severity) {
id
severity
type
message
networkId
raisedAt
source { type id }
}
}
Variables:
{
"severity": "CRITICAL"
}
Subscribe to Agent Status Changes
subscription AgentStatus($agentId: ID) {
agentStatusChanged(id: $agentId) {
agentId
oldStatus
newStatus
reason
timestamp
}
}
Subscribe to Power Flow Progress
subscription PowerFlowProgress($taskId: ID!) {
powerFlowProgress(taskId: $taskId) {
taskId
iteration
maxIterations
mismatch
timestamp
}
}
Multi-Language Code Examples
cURL
curl -X POST http://localhost:8080/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"query": "query GetNetworks { networks { items { id name status } total } }"
}'
Rust
use graphql_client::{GraphQLQuery, Response};
use serde::Serialize;
#[derive(GraphQLQuery)]
#[graphql(
schema_path = "schema.json",
query_path = "queries/get_networks.graphql",
response_derives = "Debug"
)]
pub struct GetNetworks;
#[derive(Serialize)]
pub struct Vars;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let client = reqwest::Client::new();
let req_body = GetNetworks::build_query(Vars {});
let resp = client
.post("http://localhost:8080/graphql")
.bearer_auth("<token>")
.json(&req_body)
.send()
.await?;
let response_body: Response<get_networks::ResponseData> = resp.json().await?;
if let Some(data) = response_body.data {
for net in data.networks.items {
println!("{}: {} ({:?})", net.id, net.name, net.status);
}
}
Ok(())
}
Python
import requests
url = "http://localhost:8080/graphql"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer <token>",
}
query = """
query GetNetworks {
networks {
items { id name status }
total
}
}
"""
response = requests.post(url, json={"query": query}, headers=headers)
data = response.json()
for net in data["data"]["networks"]["items"]:
print(f"{net['id']}: {net['name']} ({net['status']})")
JavaScript
import { graphqlRequest } from '@eneros/sdk';
const query = `
query GetNetworks {
networks {
items { id name status }
total
}
}
`;
const data = await graphqlRequest({
endpoint: 'http://localhost:8080/graphql',
query,
token: '<bearer-token>',
});
console.log(data.networks.items);
Subscription (JavaScript)
import { createClient } from 'graphql-ws';
const client = createClient({
url: 'ws://localhost:8080/graphql',
connectionParams: {
token: '<bearer-token>',
},
});
const unsubscribe = client.subscribe(
{
query: `
subscription OnAlarm($severity: Severity) {
alarmStream(severity: $severity) {
id
severity
message
raisedAt
}
}
`,
variables: { severity: 'CRITICAL' },
},
{
next: (data) => console.log('Alarm:', data),
error: (err) => console.error('Subscription error:', err),
complete: () => console.log('Subscription ended'),
}
);
Error Handling
GraphQL errors follow the specification, returning HTTP 200 + errors field:
{
"errors": [
{
"message": "Network not found: net_unknown",
"locations": [{"line": 2, "column": 3}],
"path": ["network"],
"extensions": {
"code": "NOT_FOUND",
"trace_id": "trace_abc123",
"details": {"id": "net_unknown"}
}
}
],
"data": null
}
Error Code Table
| Error Code | Meaning | Suggested Handling |
|---|---|---|
UNAUTHENTICATED | Not authenticated | Add Authorization Header |
FORBIDDEN | Insufficient permissions | Adjust role permissions |
NOT_FOUND | Resource does not exist | Verify ID |
VALIDATION_FAILED | Field validation failed | Check error details |
CONSTRAINT_VIOLATION | Safety constraint violated | Correct electrical parameters |
RATE_LIMITED | Rate limit triggered | Backoff and retry |
QUERY_TOO_COMPLEX | Query complexity exceeded | Split the query |
QUERY_TOO_DEEP | Query depth exceeded | Reduce nesting |
INTERNAL_ERROR | Internal error | Contact operations |
POWERFLOW_DIVERGED | Power flow did not converge | Adjust initial values |
Partial Success Response
When querying multiple fields, some may succeed while others fail. Failed fields in data will be null:
{
"data": {
"network": null,
"agents": {"items": [], "total": 0}
},
"errors": [
{
"message": "Network not found",
"path": ["network"],
"extensions": {"code": "NOT_FOUND"}
}
]
}
Introspection
Development environments support Introspection, allowing Schema metadata queries:
query IntrospectSchema {
__schema {
queryType { name }
mutationType { name }
subscriptionType { name }
types {
name
kind
fields {
name
type { name kind }
}
}
}
}
query IntrospectType($name: String!) {
__type(name: $name) {
name
kind
fields {
name
type { name kind ofType { name kind } }
args { name type { name kind } }
}
enumValues { name }
}
}
Can be enabled in production via configuration:
[graphql]
introspection = true
Performance Recommendations
- Query depth limited to 7 levels, complexity limit 1000, to prevent malicious nesting
- Time series queries should specify
aggregationto avoid returning raw high-frequency samples - Subscription auto-reconnects on disconnection, replaying the latest 100 events after reconnection
- Use persisted queries: High-frequency queries can be pre-registered to reduce network transmission
# Persisted query request
{
"extensions": {
"persistedQuery": {
"sha256Hash": "abc123...",
"version": 1
}
}
}
Related Documentation
- API Overview — Comparison of four API types
- WebSocket — Subscription underlying protocol
- REST API — REST endpoint comparison
- Rate Limit and Quota — Complexity and rate limits
- eneros-graphql crate — GraphQL service implementation