Segments and Flex Shape
Segments partition your locations into groups based on the combination of asset types present at each household. The flex shape endpoint returns a rolling 24-hour forecast of flexible load, broken down by segment.
Copy linkSegments
A segment defines a household profile — which asset types must be present and which must be absent. All assets at a matching location belong to that segment.
Segments apply across all zones. They are not tied to a specific one.
Copy linkCreating a Segment
POST /flex/segments
Content-Type: application/json
{
"name": "ev-only",
"household": {
"includes": ["vehicle"],
"excludes": ["solar", "battery"]
}
}
{
"id": "seg_abc123",
"name": "ev-only",
"household": {
"includes": ["vehicle"],
"excludes": ["solar", "battery"]
},
"createdAt": "2026-03-17T10:00:00Z",
"updatedAt": "2026-03-17T10:00:00Z"
}
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Human-readable name. Must be unique. |
household.includes | string[] | yes | Asset types the household must have. All listed types must be present. |
household.excludes | string[] | no | Asset types the household must not have. None of the listed types may be present. |
Copy linkThe Filter Language
A segment filter describes what asset types must (or must not) be present at a household:
includes— the household must have at least one asset of each listed type (AND logic)excludes— the household must not have any asset of the listed types- When
excludesis omitted, there is no restriction on additional asset types
Copy linkDisjointness
Segments are validated to be mutually exclusive on creation. If a new segment would overlap with an existing one, the request is rejected. This means every household matches at most one segment — no priority ordering is needed.
A typical set of segments for a portfolio with EVs, solar, and batteries:
// POST /flex/segments
{
"name": "ev-only",
"household": {
"includes": ["vehicle"],
"excludes": ["solar", "battery"]
}
}
// POST /flex/segments
{
"name": "ev-solar",
"household": {
"includes": ["vehicle", "solar"],
"excludes": ["battery"]
}
}
// POST /flex/segments
{
"name": "ev-battery",
"household": {
"includes": ["vehicle", "battery"]
}
}
These are disjoint because:
- ev-only requires vehicle, forbids solar and battery
- ev-solar requires vehicle and solar, forbids battery
- ev-battery requires vehicle and battery (solar allowed or not)
A household with [vehicle, solar, battery] matches ev-battery (which does not exclude solar). A household with just [vehicle] matches ev-only. No ambiguity.
Copy linkListing Segments
GET /flex/segments
Returns all segment definitions.
Copy linkUpdating a Segment
PUT /flex/segments/seg_abc123
Content-Type: application/json
{
"name": "ev-only",
"household": {
"includes": ["vehicle"],
"excludes": ["solar", "battery", "heatPump"]
}
}
The disjointness check runs against all other existing segments. If the update would create an overlap, it is rejected. Changes take effect on the next shape computation.
Copy linkDeleting a Segment
DELETE /flex/segments/seg_abc123
Assets at households previously in this segment will appear in unassigned on the next shape computation.
Copy linkFlex Shape
The flex shape endpoint returns a rolling 24-hour forecast of flexible load for a zone, broken down by segment.
Copy linkRetrieving the Shape
GET /flex/shape/NO1
To filter by area, pass the area query parameter:
GET /flex/shape/NO1?area=FA_12
GET /flex/shape/NO1?area=FA_12,FA_13
Response:
{
"zoneId": "NO1",
"computedAt": "2026-03-17T10:00:00Z",
"chunkSizeMinutes": 15,
"revisionId": "abc-def-123",
"total": {
"assetCount": 60,
"startAt": "2026-03-17T10:00:00Z",
"endAt": "2026-03-18T10:00:00Z",
"chunks": [
{
"timestamp": "2026-03-17T10:00:00Z",
"forecast": {
"expectedKw": 75000,
"minimumKw": 55000,
"maximumKw": 101000,
"confidence": 0.95
}
}
]
},
"segments": [
{
"segmentId": "seg_abc123",
"name": "ev-only",
"assetCount": 42,
"chunks": [
{
"timestamp": "2026-03-17T10:00:00Z",
"forecast": {
"expectedKw": 50000,
"minimumKw": 38000,
"maximumKw": 68000,
"confidence": 0.96
}
}
]
},
{
"segmentId": "seg_def456",
"name": "ev-solar",
"assetCount": 18,
"chunks": [
{
"timestamp": "2026-03-17T10:00:00Z",
"forecast": {
"expectedKw": 25000,
"minimumKw": 17000,
"maximumKw": 33000,
"confidence": 0.93
}
}
]
}
],
"unassigned": {
"assetCount": 0,
"chunks": []
}
}
The response contains three parts:
total— aggregates all assets in the zone regardless of segment assignmentsegments— one entry per defined segment that has matched householdsunassigned— assets at households that matched no segment
Each chunk contains a timestamp and a forecast with:
| Field | Description |
|---|---|
expectedKw | The expected flexible load at this point in time. |
minimumKw | The lower bound of the flexibility range. |
maximumKw | The upper bound of the flexibility range. |
confidence | Confidence level for the forecast interval. |
Copy linkAdditivity
Segments sum to total. For every chunk:
total.expectedKw ==
sum(segments[*].expectedKw) +
unassigned.expectedKw
total.minimumKw ==
sum(segments[*].minimumKw) +
unassigned.minimumKw
total.maximumKw ==
sum(segments[*].maximumKw) +
unassigned.maximumKw
This holds because segments are provably disjoint and each household contributes to exactly one bucket.
Copy linkSingle Segment Access
To retrieve the shape for a single segment:
GET /flex/shape/NO1/segments/seg_abc123
GET /flex/shape/NO1/segments/seg_abc123?area=FA_12
{
"startAt": "2026-03-17T10:00:00Z",
"endAt": "2026-03-18T10:00:00Z",
"chunkSizeMinutes": 15,
"computedAt": "2026-03-17T10:00:00Z",
"assetCount": 42,
"revisionId": "abc-def-123",
"chunks": [
{
"timestamp": "2026-03-17T10:00:00Z",
"forecast": {
"expectedKw": 50000,
"minimumKw": 38000,
"maximumKw": 68000,
"confidence": 0.96
}
}
]
}
Copy linkStatus
Health indicators for a zone, with a per-segment breakdown:
GET /flex/shape/NO1/status
{
"zoneId": "NO1",
"computedAt": "2026-03-17T10:00:00Z",
"isForecastStable": true,
"isSolvent": true,
"isControlNominal": true,
"segments": [
{
"segmentId": "seg_abc123",
"name": "ev-only",
"isForecastStable": true,
"isSolvent": true,
"isControlNominal": true
},
{
"segmentId": "seg_def456",
"name": "ev-solar",
"isForecastStable": false,
"isSolvent": true,
"isControlNominal": true
}
]
}
Copy linkZero-Config Behavior
If no segments are defined, the flex shape endpoint still works. All assets appear in total and unassigned, and the segments array is empty:
{
"zoneId": "NO1",
"computedAt": "2026-03-17T10:00:00Z",
"chunkSizeMinutes": 15,
"revisionId": "abc-def-123",
"total": {
"assetCount": 60,
"startAt": "2026-03-17T10:00:00Z",
"endAt": "2026-03-18T10:00:00Z",
"chunks": ["..."]
},
"segments": [],
"unassigned": {
"assetCount": 60,
"chunks": ["..."]
}
}
You can start using the flex shape endpoint immediately and add segments later when you need per-segment breakdowns.
Copy linkEndpoint Reference
| Task | Method | Endpoint |
|---|---|---|
| List segments | GET | /flex/segments |
| Create segment | POST | /flex/segments |
| Get segment | GET | /flex/segments/{segmentId} |
| Update segment | PUT | /flex/segments/{segmentId} |
| Delete segment | DELETE | /flex/segments/{segmentId} |
| Get shape (all segments) | GET | /flex/shape/{zoneId} |
| Get shape (single segment) | GET | /flex/shape/{zoneId}/segments/{segmentId} |
| Get shape status | GET | /flex/shape/{zoneId}/status |