# Tariffs This guide covers how to create and manage tariffs — the building blocks of your electricity pricing model. ## Creating a Tariff A tariff has a client-provided ID, a direction, a currency, and a unit. The tariff is created empty — you push rate data separately. ```http POST /flex/tariffs/energy-import Content-Type: application/json { "direction": "import", "currency": "EUR", "per": "kWh" } ``` The create endpoint returns `201` when a new tariff is created. If a tariff with the same ID and definition already exists, the existing resource is returned with `200`. If it exists with a different definition, the request fails with `409`. ## Pushing Rate Data Data is pushed by specifying a replacement window. All existing data in the window is replaced with the provided values. Timestamps must be timezone-aware (ISO 8601 with a UTC offset): ```http PUT /flex/tariffs/energy-import/timeseries Content-Type: application/json Idempotency-Key: 63bc5f16-8747-4206-8f4f-c53184d37ae8 { "to": "2024-06-16T00:00:00+02:00", "values": [ { "at": "2024-06-15T00:00:00+02:00", "rate": 0.12 }, { "at": "2024-06-15T06:00:00+02:00", "rate": 0.18 }, { "at": "2024-06-15T12:00:00+02:00", "rate": 0.25 }, { "at": "2024-06-15T18:00:00+02:00", "rate": 0.14 } ] } ``` `Idempotency-Key` is required on every push request. Send the same key when retrying the same push request to deduplicate retries. - Completed idempotency records are retained for 24 hours. - In-progress locks expire after 3 minutes if a request does not finish. - A request with a key currently in use returns `409 Conflict`. - Reusing a key with a different payload returns `422 Unprocessable Entity`. The first value's `at` defines the start of the replacement window — all existing data in `[firstValue.at, to)` is replaced. Each value defines the rate from its timestamp until the next value (or until `to`). In this example: - `0.12` applies from 00:00 to 06:00 - `0.18` applies from 06:00 to 12:00 - `0.25` applies from 12:00 to 18:00 - `0.14` applies from 18:00 to 24:00 Rules: - At least one value is required - Values must be ordered chronologically - The first value's timestamp must be at least 1 hour in the future - Data outside the `[firstValue.at, to)` window is untouched You can push data for overlapping windows — each push fully replaces the specified window. Flat or fixed-rate tariffs must be periodically extended with new pushes before the current coverage expires. Overwriting existing data counts against a per-client overwrite budget. Each client has a sliding 24-hour budget of overwrite points. If a push would exceed the budget, it is rejected with `429 Too Many Requests`. Pushes that only extend coverage (no existing data in the window) are not limited. Avoid resending the same future data repeatedly — for example, instead of pushing 365 days of rates every day, prefer sending the next 48 hours daily or pushing a large batch once. ## Data Availability A tariff tracks the outer bounds of the time range it has data for. As you push data, this range expands. The tariff resource exposes this as `availableFrom` and `availableTo`: ```json { "id": "energy-import", "direction": "import", "currency": "EUR", "per": "kWh", "availableFrom": "2024-06-14T22:00:00Z", "availableTo": "2024-06-16T22:00:00Z", "createdAt": "2024-06-14T10:00:00Z", "updatedAt": "2024-06-15T08:30:00Z" } ``` Since rate data uses timezone-aware timestamps, `availableFrom` and `availableTo` are in UTC. When querying a range that extends beyond the available data, intervals outside the available range are returned as unresolved. This prevents stale rates from silently carrying forward beyond the explicitly pushed range. There are no open-ended tariffs — all tariffs have a finite coverage window that must be explicitly extended. Coverage gaps within the available range are also possible — if pushes don't cover the full `[availableFrom, availableTo)` window, the tariff will have holes where no rate data exists. When a tariff is used in a formula, the resolved tariff endpoint marks intervals as unresolved for any portion where an input tariff lacks data — whether beyond the available range or within a coverage gap. Only intervals where all input tariffs have data produce resolved results. ## Querying Tariff Data The timeseries endpoint returns a tariff's rate data as timezone-aware timestamps for a given time range. The stored UTC data is annotated with the requested timezone's offset. The `from` and `to` parameters are local dates (e.g., `2024-06-15`). This avoids ambiguity during DST transitions, since DST changes never occur at midnight. The `timezoneName` parameter is required. ```http GET /flex/tariffs/energy-import/timeseries?from=2024-06-15&to=2024-06-16&timezoneName=Europe/Berlin ``` ```json { "tariffId": "energy-import", "direction": "import", "currency": "EUR", "per": "kWh", "from": "2024-06-15", "to": "2024-06-16", "timezoneName": "Europe/Berlin", "values": [ { "at": "2024-06-15T00:00:00+02:00", "rate": 0.12 }, { "at": "2024-06-15T06:00:00+02:00", "rate": 0.18 }, { "at": "2024-06-15T12:00:00+02:00", "rate": 0.25 }, { "at": "2024-06-15T18:00:00+02:00", "rate": 0.14 } ] } ``` ## Listing and Deleting Tariffs ### List All Tariffs ```http GET /flex/tariffs ``` Supports pagination and an optional `source` filter: ```http GET /flex/tariffs?source=user ``` ### Delete a Tariff ```http DELETE /flex/tariffs/energy-import ``` A tariff cannot be deleted if it is referenced by any location's formula. ## Endpoint Reference | Task | Method | Endpoint | |------|--------|----------| | List tariffs | GET | `/flex/tariffs` | | Create tariff | POST | `/flex/tariffs/{tariffId}` | | Get tariff | GET | `/flex/tariffs/{tariffId}` | | Delete tariff | DELETE | `/flex/tariffs/{tariffId}` | | Query tariff data | GET | `/flex/tariffs/{tariffId}/timeseries` | | Push rate data | PUT | `/flex/tariffs/{tariffId}/timeseries` | [Next: Formulas & Resolved Tariffs](https://flex.developers.enode.com/docs/tariffs/formulas-and-resolved-tariffs)