Login

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:

  1. Variables — a map from short names to tariff IDs
  2. Formula — an expression that combines the variables
  3. 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

OperatorDescriptionExample
+Additionenergy + grid
-Subtractiongross - discount
-Negation-spot
*Multiplication1.15 * spot
/Divisionspot / 1000
( )Grouping(spot + grid) * markup

Copy linkCommon Functions

FunctionDescriptionExample
min(a, b)Minimum of two valuesmin(spot, 0.50)
max(a, b)Maximum of two valuesmax(spot, 0)
clamp(x, lo, hi)Constrain between boundsclamp(spot, 0, 0.50)
abs(x)Absolute valueabs(spot)
round(x, n)Round to n decimal placesround(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.
OperationResultValid?
rate + raterateYes
rate - raterateYes
scalar × raterateYes
rate × rateNo
scalar + rateNo

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

TaskMethodEndpoint
Get formulasGET/flex/locations/{locationId}/tariff-formulas
Set formulaPUT/flex/locations/{locationId}/tariff-formulas
Delete formulaDELETE/flex/locations/{locationId}/tariff-formulas?direction={direction}
Get resolved tariffsGET/flex/locations/{locationId}/tariffs/resolved
Was this article helpful?