Formulas & Resolved Tariffs
Formulas define how tariffs combine into a final rate at a specific location. You configure a formula per location and direction, and the system evaluates it to produce a resolved tariff timeseries.
Copy linkConfiguring a Formula
A formula has three parts:
- Variables — a map from short names to tariff IDs
- Formula — an expression that combines the variables
- Direction — whether this formula is for import or export
Copy linkExample: Energy + Grid Fee + Margin
Given two existing tariffs — an energy tariff (energy-import) and a grid tariff (grid-import) — you can combine them with a fixed margin:
PUT /flex/locations/{locationId}/tariff-formulas
Content-Type: application/json
{
"direction": "import",
"variables": {
"energy": "energy-import",
"grid": "grid-import"
},
"formula": "energy + grid + 0.02"
}
This computes the final rate as: energy tariff rate + grid tariff rate + 2 cents/kWh margin.
Copy linkExample: Spot Price with Floor and Markup
PUT /flex/locations/{locationId}/tariff-formulas
Content-Type: application/json
{
"direction": "import",
"variables": {
"spot": "spot-energy",
"grid": "grid-import"
},
"formula": "max(spot, 0) * 1.15 + grid + 0.03"
}
This floors the spot price at zero (no negative prices), applies a 15% markup, adds the grid fee, and adds a 3 cent/kWh margin.
Copy linkFormula Syntax
Copy linkOperators
| Operator | Description | Example |
|---|---|---|
+ | Addition | energy + grid |
- | Subtraction | gross - discount |
- | Negation | -spot |
* | Multiplication | 1.15 * spot |
/ | Division | spot / 1000 |
( ) | Grouping | (spot + grid) * markup |
Copy linkCommon Functions
| Function | Description | Example |
|---|---|---|
min(a, b) | Minimum of two values | min(spot, 0.50) |
max(a, b) | Maximum of two values | max(spot, 0) |
clamp(x, lo, hi) | Constrain between bounds | clamp(spot, 0, 0.50) |
abs(x) | Absolute value | abs(spot) |
round(x, n) | Round to n decimal places | round(spot + grid, 4) |
Additional arithmetic functions are available beyond those listed here, but the above cover the vast majority of tariff use cases.
Copy linkDecimal Literals
You can use decimal numbers directly in formulas: 0.03, 1.15, 0. These are treated as dimensionless scalars when used with * or /, or as rates (currency/kWh) when used with + or -.
Copy linkDimensional Rules
The formula system enforces dimensional correctness. There are two types of values:
- Rate — a price in currency per kWh (e.g., 0.28 EUR/kWh). Tariffs with
per: "kWh"produce rate values. - Scalar — a dimensionless multiplier (e.g., 1.15). Tariffs with
per: "scalar"produce scalar values.
| Operation | Result | Valid? |
|---|---|---|
| rate + rate | rate | Yes |
| rate - rate | rate | Yes |
| scalar × rate | rate | Yes |
| rate × rate | — | No |
| scalar + rate | — | No |
The formula must evaluate to a rate. The API validates dimensional correctness when you create a formula and returns an error if the formula is invalid.
Copy linkCurrency and Direction Rules
All rate-valued tariffs in a formula must share the same currency. The formula's direction must match the direction of all referenced tariffs (including scalar tariffs). The API validates these constraints when you create a formula.
Copy linkScalar Tariffs
If a coefficient in your formula changes over time, use a scalar tariff instead of a literal.
For example, if the spot markup factor varies, create a scalar tariff to hold it:
POST /flex/tariffs/spot-markup
Content-Type: application/json
{
"direction": "import",
"per": "scalar"
}
Scalar tariffs have no currency since they're dimensionless. They still require a direction, which is validated against the formula's direction.
Push coefficient values:
PUT /flex/tariffs/spot-markup/timeseries
Content-Type: application/json
Idempotency-Key: a1b2c3d4-e5f6-7890-abcd-ef1234567890
{
"to": "2024-06-16T00:00:00+02:00",
"values": [
{ "at": "2024-06-15T00:00:00+02:00", "rate": 1.15 },
{ "at": "2024-06-15T12:00:00+02:00", "rate": 1.20 }
]
}
Then use it in a formula:
PUT /flex/locations/{locationId}/tariff-formulas
Content-Type: application/json
{
"direction": "import",
"variables": {
"spot": "spot-energy",
"markup": "spot-markup",
"grid": "grid-import"
},
"formula": "max(spot, 0) * markup + grid + 0.02"
}
Now the markup changes from 1.15 to 1.20 at midday without updating the formula itself.
Copy linkRetrieving Formulas
GET /flex/locations/{locationId}/tariff-formulas
Returns formulas for all directions. Filter by direction:
GET /flex/locations/{locationId}/tariff-formulas?direction=import
Copy linkDeleting a Formula
DELETE /flex/locations/{locationId}/tariff-formulas?direction=import
Copy linkResolved Tariffs
The resolved tariffs endpoint evaluates the location's formula over a time range and returns the computed rate at each interval.
GET /flex/locations/{locationId}/tariffs/resolved?from=2024-06-15&to=2024-06-16&direction=import
{
"locationId": "4eaeb363-296d-4ccc-a973-7805e6f400bd",
"direction": "import",
"currency": "EUR",
"per": "kWh",
"from": "2024-06-15",
"to": "2024-06-16",
"timezoneName": "Europe/Berlin",
"intervals": [
{
"type": "resolved",
"startAt": "2024-06-15T00:00:00+02:00",
"endAt": "2024-06-15T01:00:00+02:00",
"formula": "max(spot, 0) * 1.15 + grid + 0.03",
"rate": 0.248
},
{
"type": "resolved",
"startAt": "2024-06-15T01:00:00+02:00",
"endAt": "2024-06-15T02:00:00+02:00",
"formula": "max(spot, 0) * 1.15 + grid + 0.03",
"rate": 0.225
}
]
}
Copy linkHow Intervals Work
Each interval has a type — either resolved (with a computed rate) or unresolved (indicating missing data). A new resolved interval starts whenever any input tariff's rate changes (e.g., the spot price updates hourly).
Copy linkUnavailable Data
If a tariff in the formula doesn't have data covering the entire requested range, intervals outside the tariff's available range are returned as unresolved. The available range is determined by the data that has been pushed — each tariff's availableFrom and availableTo define the window where rates are valid. Beyond this window, rates are not carried forward, preventing stale data from silently producing incorrect prices.
For example, if grid has data pushed until 12:00 (availableTo = 12:00), querying 00:00–24:00 returns:
{
"intervals": [
{
"type": "resolved",
"startAt": "2024-06-15T00:00:00+02:00",
"endAt": "2024-06-15T12:00:00+02:00",
"formula": "max(spot, 0) * 1.15 + grid + 0.03",
"rate": 0.248
},
{
"type": "unresolved",
"startAt": "2024-06-15T12:00:00+02:00",
"endAt": "2024-06-16T00:00:00+02:00"
}
]
}
Only intervals where all input tariffs have data produce resolved results. If any tariff's available range doesn't cover a portion of the requested range, that portion is unresolved. When multiple tariffs are used, only the intersection of their available ranges produces resolved intervals.
Copy linkEndpoint Reference
| Task | Method | Endpoint |
|---|---|---|
| Get formulas | GET | /flex/locations/{locationId}/tariff-formulas |
| Set formula | PUT | /flex/locations/{locationId}/tariff-formulas |
| Delete formula | DELETE | /flex/locations/{locationId}/tariff-formulas?direction={direction} |
| Get resolved tariffs | GET | /flex/locations/{locationId}/tariffs/resolved |