Login

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
namestringyesHuman-readable name. Must be unique.
household.includesstring[]yesAsset types the household must have. All listed types must be present.
household.excludesstring[]noAsset 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 excludes is 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 assignment
  • segments — one entry per defined segment that has matched households
  • unassigned — assets at households that matched no segment

Each chunk contains a timestamp and a forecast with:

Field Description
expectedKwThe expected flexible load at this point in time.
minimumKwThe lower bound of the flexibility range.
maximumKwThe upper bound of the flexibility range.
confidenceConfidence 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 segmentsGET/flex/segments
Create segmentPOST/flex/segments
Get segmentGET/flex/segments/{segmentId}
Update segmentPUT/flex/segments/{segmentId}
Delete segmentDELETE/flex/segments/{segmentId}
Get shape (all segments)GET/flex/shape/{zoneId}
Get shape (single segment)GET/flex/shape/{zoneId}/segments/{segmentId}
Get shape statusGET/flex/shape/{zoneId}/status
Was this article helpful?