This is the full developer documentation for Cofy
# Cofy documentation
> Find the part of the Cofy docs that fits what you want to do.
New to Cofy? Start with the [concepts](/docs/concepts/): communities, modules, sources and resources, in a few minutes.
## Manage your community in the console
[Section titled “Manage your community in the console”](#manage-your-community-in-the-console)
For community managers. No code needed.
[Use the console](/docs/console/)Log in, add modules, and connect your apps.
[Choose modules and sources](/docs/modules/)What each module serves, and where its data can come from.
## Run your own Cofy
[Section titled “Run your own Cofy”](#run-your-own-cofy)
For whoever runs the server.
[Run one community](/docs/self-host/single-community/)Install Cofy, and describe your community in a settings file.
[The settings file](/docs/self-host/settings-file/)Everything a community's settings can hold.
## Calculate energy costs
[Section titled “Calculate energy costs”](#calculate-energy-costs)
[Energy cost](/docs/energy-cost/)Write tariffs and contracts, and calculate bills, inside Cofy or on their own.
## Build on Cofy
[Section titled “Build on Cofy”](#build-on-cofy)
[Build and extend](/docs/extend/)Write your own sources and modules, or your own console.
[Use Cofy with an AI agent](/docs/agents/)Let an agent read these docs and help you.
# Use Cofy with AI agents
> Let an AI agent help you with Cofy.
We’re working on letting AI agents help with Cofy: setting up a community through the management API, writing a settings file, or adding a source of your own.
Until then, an agent can already read these docs in one go. Point it at:
* [/llms.txt](/llms.txt): an index of the docs;
* [/llms-full.txt](/llms-full.txt): all of the docs in a single file.
With them, an agent can answer questions about modules and sources, or write the YAML for a module, a [settings file](/docs/self-host/settings-file/) or an [energy-cost tariff](/docs/energy-cost/tariffs/), for you to paste into the [console](/docs/console/modules/) or your own setup.
# Concepts
> The handful of ideas everything else in Cofy builds on.
A few words come back everywhere in Cofy. This page explains them once, so the rest of the docs can use them.
## Community
[Section titled “Community”](#community)
A **community** is one energy community, or one supplier’s setup. Each community gets its own **Cofy API**: a web API that serves only that community’s data.
## Module
[Section titled “Module”](#module)
A **module** is one thing a community’s API can do. You switch on only the modules you need:
| Module | What it does |
| -------------- | ------------------------------------------------------------------------- |
| `members` | The community’s members |
| `production` | Local energy production, as time series |
| `tariff` | Energy prices, as time series |
| `billing` | Calculates energy costs from meter data and a contract |
| `simultaneity` | How much consumption overlaps with local production, as time series |
| `directive` | A five-step signal, from `--` to `++`, telling members when to use energy |
| `timeseries` | Any other time series, such as a solar forecast |
Each module has [its own page](/docs/modules/).
## Source
[Section titled “Source”](#source)
A **source** is where a module gets its data from, such as EnergyID meter data, ENTSO-E day-ahead prices, or Elia and NED solar forecasts. Each module only accepts sources that fit it: a tariff module only takes prices, for example. [Sources](/docs/sources/) lists them all.
## Format
[Section titled “Format”](#format)
A **format** is the shape a module serves its data in. Most modules can offer the same data in more than one format.
## Resource
[Section titled “Resource”](#resource)
A **resource** is something configured once and used in several places, such as a source that several modules share. A shared source is fetched once, so every module using it shares its cache.
## Secret
[Section titled “Secret”](#secret)
A **secret** is a credential, such as an API key. It is stored once per community and referenced by name. The management API never sends a secret’s value back.
# The management console
> Configure your community's Cofy API from your browser, without writing code.
The management console is where you set up and change your community’s Cofy API. Everything you change there is live within seconds; you never install or restart anything.
## Logging in
[Section titled “Logging in”](#logging-in)
Open the console’s address and log in with your account at your Cofy host, such as your EnergyID account for the Cofy that EnergyID hosts. Use the email address you were given access with.
If you see no communities after logging in, nobody has given you access yet: ask whoever manages your community, or your Cofy host, to [give you access](/docs/console/access/).
## Your communities
[Section titled “Your communities”](#your-communities)
The first page lists the communities you have access to, with how many modules each has and the status of its API. Click a community to open it.
At the top of a community’s page you find:
* **Its API address**, with a button to copy it. Apps use this address to reach your community’s data; see [connect an app](/docs/console/api-tokens/).
* **The status of its API**:
| Status | What it means |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Live | The API runs your latest saved settings. |
| Changes pending | The API still runs the previous settings. Saved changes normally apply within a few seconds; if this stays, the new settings could not be applied: ask your Cofy host. |
| Unavailable | The API is not running. Ask your Cofy host. |
Below them, a community has these tabs:
| Tab | What you do there |
| ----------------------------------------------------------- | ---------------------------------------------------------------------- |
| [Modules](/docs/console/modules/) | Switch on the modules your community needs, and configure each one |
| [Resources](/docs/console/resources-and-secrets/#resources) | Configure things several modules share, like a data source or a tariff |
| [Secrets](/docs/console/resources-and-secrets/#secrets) | Store credentials, such as an API key, without showing them again |
| [API tokens](/docs/console/api-tokens/) | Create the keys other applications use to call your community’s API |
| [Access](/docs/console/access/) | Decide who else can manage this community |
You only see the tabs you are allowed to use.
## Your settings
[Section titled “Your settings”](#your-settings)
The button at the top right opens your settings: light or dark appearance, the console’s language, and logging out.
## Where to start
[Section titled “Where to start”](#where-to-start)
1. Read the [concepts](/docs/concepts/), to know what modules, sources and resources are.
2. [Add your first module](/docs/console/modules/#adding-a-module).
3. [Create an API token](/docs/console/api-tokens/) for the app that will use it.
# Give others access
> Let others manage your community in the console.
The Access tab lists who may manage this community. You see it if you are a community admin.
## Giving someone access
[Section titled “Giving someone access”](#giving-someone-access)
1. On the Access tab, click **Grant access**.
2. Enter the **email** address the person logs in with.
3. Choose their **role**.
4. Click **Grant**.
They can log in right away. Until they do, their status is **Awaiting first login**; at their first login, access is tied to their account, and their status becomes **Account linked**. From then on, it stays theirs even if they change the email address of their account.
The person needs an account at your Cofy host, with a verified email address.
## Roles
[Section titled “Roles”](#roles)
| Role | May |
| --------------- | -------------------------------------------------------------------------------------------------- |
| Community admin | Everything in this community: its modules, resources, secrets, API tokens, and who else has access |
## Taking access away
[Section titled “Taking access away”](#taking-access-away)
Click **Revoke** beside the person. From then on they can no longer see or change the community.
# Connect an app
> Create API tokens, and let an app read your community's data.
Apps, such as your community’s website, an energy app or a home battery, read your community’s data from its API. Each app gets its own **API token**: a key that proves it may.
## Creating a token
[Section titled “Creating a token”](#creating-a-token)
1. On the API tokens tab, click **New token**.
2. Give it a **name**, such as `website`, and a description of which app uses it.
3. Optionally, choose a date from which it **expires**.
4. Click **Create**.
5. Copy the **key** and give it to the app’s developer, or paste it into the app.
Caution
The key is shown only once. Cofy doesn’t store it, so it can never be shown again. If you lose it, delete the token and create a new one.
Once your community has a token, every request to its API needs one.
Caution
Until you create the first token, your community’s API is open to anyone who knows its address.
## What the app needs
[Section titled “What the app needs”](#what-the-app-needs)
Give the app’s developer:
* the API address, from the top of your community’s page;
* the key;
* the addresses of the modules it should use, such as `/tariff/day-ahead/v1`;
* a link to [these docs](/docs/modules/).
An app sends the key in a header, `Authorization: Bearer `, or, where it can’t set headers, in the address: `?token=`.
## Trying it in your browser
[Section titled “Trying it in your browser”](#trying-it-in-your-browser)
Every community’s API describes itself. Open your API address followed by `/docs?token=`: you see every module, and can try each one with **Try it out**.
## Stopping an app
[Section titled “Stopping an app”](#stopping-an-app)
Delete its token on the API tokens tab: from then on, the app can no longer reach your API. The other apps keep working, since each has its own token.
# Modules
> Add, change and remove the modules of your community.
The Modules tab lists what your community’s API serves. Each [module](/docs/concepts/#module) has a page in these docs describing what it does and every setting it has: see [modules](/docs/modules/).
Copy an example from these docs
Every module and source page in these docs has YAML examples you can use as they are. Click **View as YAML** at the top of the module form, paste the example, and adjust it. **View as form** takes you back, and the **⋮** menu of a single field does the same for just that field. Whatever you enter is checked as you type.
## Adding a module
[Section titled “Adding a module”](#adding-a-module)
1. On the Modules tab, click **Add module**.
2. Choose the **module type**, such as Tariff. Only the types your community may use are offered.
3. Give it a **name**. The name becomes part of the module’s address, such as `/tariff/dynamic/v1`, so keep it short: lowercase letters, digits, `-` and `_`. Leave it as `default` if your community has only one module of this type.
4. Optionally, give it a **display name** and a **description**, so others know what it is for.
5. Fill in the rest of the form, such as the module’s **source**: where its data comes from.
6. Click **Create module**.
The module appears in the list, and the community’s [status](/docs/console/#your-communities) shows **Changes pending** for a few seconds, then **Live** again. From then on, the module is served at your API address followed by `/{type}/{name}/v1`.
### Example: day-ahead prices
[Section titled “Example: day-ahead prices”](#example-day-ahead-prices)
To serve the Belgian day-ahead market price, with an API key from ENTSO-E:
1. On the [Secrets](/docs/console/resources-and-secrets/#secrets) tab, add a secret named `entsoe_key`, with your ENTSO-E API key as its value.
2. Add a module of type Tariff, named `day-ahead`.
3. Under **Source**, choose the type Entsoe Day Ahead.
4. Under **Api Key**, choose the secret `entsoe_key`.
5. Click **Create module**.
The prices are now at `/tariff/day-ahead/v1`. The [ENTSO-E](/docs/sources/entsoe/) page lists what else you can set, such as the country.
## Filling in the form
[Section titled “Filling in the form”](#filling-in-the-form)
The form follows the module you chose. Its fields are the settings listed on the module’s page and its source’s page, in words: the setting `api_key` is the field Api Key, and the source type `entsoe_day_ahead` is Entsoe Day Ahead.
A few things work the same everywhere:
* **Choosing a type.** Where a setting can be one of several kinds, such as a source, choose its type first; the fields for that type then appear below it.
* **Required fields** are marked with a `*`. You can’t save while one is empty or a value is invalid: the field says what’s wrong, and the button shows **Saving blocked by errors**.
* **Optional parts** that aren’t there yet show a **Create** button. Click it to add them; the **⋮** menu beside them removes them again.
* **Lists**, such as a list of tariff versions, have an **Add** button, and each entry can be removed.
* **Advanced options** are folded away at the end of a section. You rarely need them; each is explained on the page of its module or source.
* **Credentials** are never typed into the form itself: choose one of your community’s [secrets](/docs/console/resources-and-secrets/#secrets), or create a new one on the spot.
* **Resources.** Where a source or tariff fits, its **⋮** menu offers **Use a resource**, to pick one you configured on the [Resources](/docs/console/resources-and-secrets/#resources) tab, and **Save as resource**, to turn what you filled in into one, so other modules can use it too.
## Changing a module
[Section titled “Changing a module”](#changing-a-module)
Click a module in the list to open it. Change what you need and click **Save**, or **Discard** to undo your changes. The heading tells you whether there are unsaved changes.
A module’s type and name can’t be changed, because apps use them in its address. To rename one, add a new module and delete the old one.
## Deleting a module
[Section titled “Deleting a module”](#deleting-a-module)
Click **Delete** beside the module in the list. Its address stops working within seconds, and its settings can’t be recovered.
# Resources and secrets
> Configure something once and use it in several modules, and keep credentials safe.
## Resources
[Section titled “Resources”](#resources)
A [resource](/docs/concepts/#resource) is a value you configure once and use in several places. There are three kinds:
| Kind | What it holds | Use it for |
| --------------- | --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `source` | A time series source | One source used by several modules, such as day-ahead prices for both a tariff and a directive. It is fetched once, so the modules also share what it has cached. |
| `member_source` | Where the members list comes from | The [members module](/docs/modules/members/) |
| `tariff` | A tariff, in [energy-cost’s format](/docs/energy-cost/tariffs/) | A tariff served by a tariff module and also used elsewhere |
### Adding a resource
[Section titled “Adding a resource”](#adding-a-resource)
1. On the Resources tab, click **Add resource**.
2. Choose its **kind**, give it a **name** and, optionally, a description.
3. Fill in its value, as you would in a module’s form.
4. Click **Create resource**.
Or, while filling in a module, open the **⋮** menu of a source or tariff and choose **Save as resource**: what you filled in becomes a resource, and the module uses it.
### Using a resource
[Section titled “Using a resource”](#using-a-resource)
In a module’s form, open the **⋮** menu of a field where a resource fits and choose **Use a resource**, then pick it. **Specify a value** switches back to filling it in.
Changing a resource changes it for every module that uses it. A resource that is still in use can’t be deleted; remove it from those modules first.
## Secrets
[Section titled “Secrets”](#secrets)
A [secret](/docs/concepts/#secret) is a credential, such as an API key or a password. You store it once, on the Secrets tab, and modules refer to it by name.
1. On the Secrets tab, click **Add secret**.
2. Give it a **name**, such as `entsoe_key`, and, optionally, a description of what it is for.
3. Paste its **value** and click **Save**.
A secret’s value is never shown again, not even to you. To change it, edit the secret and enter the new value: every module using it picks up the new value.
In a module’s form, a credential field lets you **choose a secret**, or create a **new secret** on the spot.
Tip
Some credentials are a whole block of JSON, such as the key for an [EvO](/docs/sources/evo/) forecast. Paste it as it is.
# Energy cost
> Model a complete energy bill, on its own or inside Cofy.
[energy-cost](https://github.com/EnergieID/energy-cost) is an open-source Python library that calculates a complete energy bill from metered consumption and injection. It’s what Cofy’s [billing module](/docs/modules/billing/) and [`energy_cost` source](/docs/sources/energy-cost/) use, and it also works on its own.
A bill is calculated from a **contract**, which brings together:
| Part | What it is |
| --------------- | --------------------------------------------- |
| **supplier** | The energy supplier’s tariff |
| **distributor** | The grid operator’s tariff |
| **fees** | Government fees and levies, such as excise |
| **taxes** | VAT on each part of the bill |
| **timezone** | The time zone all dates and times are read in |
You rarely write all of it. Give a contract a region, connection type, customer type and distributor, and energy-cost fills in the distributor’s tariff, the fees, the taxes and the time zone from its [built-in data](/docs/energy-cost/built-in-data/). Then you only write the supplier’s tariff:
```yaml
region: be_flanders
connection_type: electricity
customer_type: residential
distributor_key: fluvius_imewo
supplier:
- start: 2025-01-01T00:00:00+01:00
consumption:
energy: { constant_cost: 95.0 }
```
## Where to go next
[Section titled “Where to go next”](#where-to-go-next)
* [Contracts](/docs/energy-cost/contracts/): every part of a contract, and how built-in data is filled in.
* [Tariffs](/docs/energy-cost/tariffs/): how to write a tariff, from a fixed price to time-of-use, tiers and dynamic prices.
* [Taxes and capacity](/docs/energy-cost/taxes-and-capacity/): VAT rates and capacity tariffs.
* [Built-in data](/docs/energy-cost/built-in-data/): the regions, distributors, fees and taxes energy-cost knows.
* [Examples](/docs/energy-cost/examples/): complete contracts to start from.
* [Using it from Python](/docs/energy-cost/python/): calculate a bill in your own code.
## Units
[Section titled “Units”](#units)
energy-cost works in euro, MWh and MW:
| Quantity | Unit |
| --------------- | --------------------------------------------------- |
| Energy prices | €/MWh. 1 c/kWh is 10 €/MWh, so 9.5 c/kWh is `95.0`. |
| Fixed costs | € per period, such as € per year |
| Capacity prices | € per MW |
| Tax rates | A fraction: 6% is `0.06` |
| The bill | € |
**A positive cost is paid by the customer; a negative cost is received.** That holds for injection too: a price you pay for the energy you inject is positive, and a price you receive for it is negative.
## Inside Cofy
[Section titled “Inside Cofy”](#inside-cofy)
* The [billing module](/docs/modules/billing/) calculates a bill from meter data and a contract sent with each request.
* The [`energy_cost` source](/docs/sources/energy-cost/) serves the prices of a tariff through the [tariff module](/docs/modules/tariff/), for example your own dynamic tariff.
# Built-in data
> The regions, distributors, fees and taxes energy-cost knows.
For a region it knows, energy-cost fills in a [contract](/docs/energy-cost/contracts/#how-built-in-data-is-filled-in): the distributor’s tariff, the fees, the taxes, the capacity rule and the time zone. For now that’s Flanders, for residential and small business connections with a digital meter.
## Flanders: `be_flanders`
[Section titled “Flanders: be_flanders”](#flanders-be_flanders)
Time zone `Europe/Brussels`.
### Distributors
[Section titled “Distributors”](#distributors)
The Fluvius grid areas, for both electricity and gas:
| `distributor_key` | Data from |
| --------------------------- | --------- |
| `fluvius_antwerpen` | 2024 |
| `fluvius_imewo` | 2024 |
| `fluvius_limburg` | 2024 |
| `fluvius_west` | 2024 |
| `fluvius_halle_vilvoorde` | 2025 |
| `fluvius_kempen` | 2025 |
| `fluvius_midden_vlaanderen` | 2025 |
| `fluvius_zenne_dijle` | 2025 |
Their tariffs come from the VREG, for low voltage.
* **Electricity:** transmission, levies and a public service fee per MWh, a yearly data management fee, and the capacity tariff on the monthly peak, with its yearly minimum.
* **Gas:** Fluvius’s distribution fees and levies, in bands by yearly use, plus Fluxys’s transport.
### Electricity
[Section titled “Electricity”](#electricity)
| `customer_type` | Fees |
| ----------------- | --------------------------------------------------------------------------- |
| `residential` | Federal excise and energy contribution, and in 2022 the Flemish energy fund |
| `non_residential` | Federal excise and energy contribution, and the Flemish energy fund |
| `protected` | Federal excise and energy contribution, as for protected customers |
* **Taxes:** 6% VAT, none on the energy fund or on injection.
* **Capacity rule:** the Flemish capacity tariff, see [capacity](/docs/energy-cost/taxes-and-capacity/#capacity). Meter data must be per quarter-hour or finer.
### Gas
[Section titled “Gas”](#gas)
* **Fees:** federal excise and energy contribution.
* **Taxes:** 6% VAT.
* No capacity rule.
## Coverage
[Section titled “Coverage”](#coverage)
Fees and taxes start in 2022; distributor tariffs in 2024 or 2025, as above.
* **Before** a tariff’s first version, it has no price: a bill over that time is incomplete, or leaves that part out.
* **After** its last version, the last prices keep applying. Keep energy-cost up to date for new years’ tariffs.
## Adding a region
[Section titled “Adding a region”](#adding-a-region)
The [energy-cost repository](https://github.com/EnergieID/energy-cost) explains how built-in data is organised, and how to contribute the data for a region it doesn’t know yet. Until then, write the distributor, fees, taxes and time zone into the contract yourself.
# Contracts
> Every part of an energy-cost contract, and how built-in data fills it in.
A contract describes everything that makes up one connection’s bill. In YAML it is a single mapping.
## Fields
[Section titled “Fields”](#fields)
| Field | What it is |
| ----------------------------- | --------------------------------------------------------------------------------------------------------- |
| `region` | The region whose [built-in data](/docs/energy-cost/built-in-data/) to use, such as `be_flanders` |
| `connection_type` | `electricity` or `gas`. Needed for any built-in data. |
| `customer_type` | `residential`, `non_residential` or `protected`. Picks the region’s fees. |
| `distributor_key` | The grid operator within the region, such as `fluvius_imewo`. Picks the distributor’s tariff. |
| `supplier` | The supplier’s [tariff](/docs/energy-cost/tariffs/) |
| `supplier_key`, `product_key` | Instead of `supplier`: a supplier and product registered [in Python](/docs/energy-cost/python/#suppliers) |
| `distributor` | A [tariff](/docs/energy-cost/tariffs/) replacing the region’s distributor tariff |
| `fees` | A [tariff](/docs/energy-cost/tariffs/), or a list of tariffs, replacing the region’s fees |
| `taxes` | [Taxes](/docs/energy-cost/taxes-and-capacity/#taxes) replacing the region’s |
| `capacity_rule` | A [capacity rule](/docs/energy-cost/taxes-and-capacity/#capacity) replacing the region’s |
| `timezone` | A time zone name such as `Europe/Brussels`. The region’s if it has one, otherwise `UTC`. |
| `start`, `end` | When the contract applies, in a [contract history](#a-contract-that-changes) |
Every field is optional, but a bill needs at least one tariff.
## How built-in data is filled in
[Section titled “How built-in data is filled in”](#how-built-in-data-is-filled-in)
With a `region` and a `connection_type`, energy-cost fills in what you leave out:
* the **time zone**, from the region;
* the **taxes** and the **capacity rule**, from the region;
* the **fees**, from the region, if you give a `customer_type`;
* the **distributor** tariff, from the region, if you give a `distributor_key`.
Anything you write yourself replaces the built-in part as a whole: a `fees` tariff replaces all of the region’s fees, it isn’t added to them. Without a `customer_type` there are no fees, and without a `distributor_key` there is no distributor cost.
Caution
energy-cost ignores fields it doesn’t know. A misspelling such as `supplierr:` gives no error, just a bill without that part. If a part of the bill is missing, check the spelling first.
## Several tariffs in one part
[Section titled “Several tariffs in one part”](#several-tariffs-in-one-part)
`supplier`, `distributor` and `fees` can also be a list of tariffs, whose costs are added up. That’s how the built-in data for Flanders combines the Flemish fees with the federal ones. Each tariff in the list is itself a list of versions, so the list has two levels:
```yaml
fees:
- - start: 2025-01-01T00:00:00+01:00
fixed:
regional_fund: { period: P1M, constant_cost: 1.5 }
- - start: 2025-01-01T00:00:00+01:00
consumption:
national_levy: { constant_cost: 2.0 }
```
## A contract that changes
[Section titled “A contract that changes”](#a-contract-that-changes)
A **contract history** is a list of contracts, each with a `start` and, optionally, an `end`. Each one applies from its `start` until its `end`, or until the next one starts:
```yaml
- start: 2025-01-01T00:00:00+01:00
end: 2025-07-01T00:00:00+02:00
region: be_flanders
connection_type: electricity
customer_type: residential
distributor_key: fluvius_imewo
supplier:
- start: 2025-01-01T00:00:00+01:00
consumption: { constant_cost: 110.0 }
- start: 2025-07-01T00:00:00+02:00
region: be_flanders
connection_type: electricity
customer_type: residential
distributor_key: fluvius_imewo
supplier:
- start: 2025-07-01T00:00:00+02:00
consumption: { constant_cost: 95.0 }
```
A period that no contract covers is left out of the bill. A single contract, not in a list, ignores its `start` and `end`.
Use a contract history for a change of supplier, address or customer type. For a price change with the same supplier, add a [version](/docs/energy-cost/tariffs/#versions) to the tariff instead.
## Dates and times
[Section titled “Dates and times”](#dates-and-times)
Write dates and times in ISO 8601 with their offset: `2025-01-01T00:00:00+01:00`. A date or time without an offset is read in the contract’s time zone. Around the change to winter time, a time that occurs twice is taken as the second, in winter time; a time skipped by the change to summer time moves forward.
# Examples
> Complete energy-cost contracts to start from.
Each example is a complete [contract](/docs/energy-cost/contracts/) for a residential electricity connection in Flanders. The distributor, fees and taxes come from the [built-in data](/docs/energy-cost/built-in-data/); only the supplier’s tariff is written out.
## Fixed price, with solar panels
[Section titled “Fixed price, with solar panels”](#fixed-price-with-solar-panels)
9.5 c/kWh for consumption, 3 c/kWh received for injection, and a subscription of 60 € a year:
```yaml
region: be_flanders
connection_type: electricity
customer_type: residential
distributor_key: fluvius_imewo
supplier:
- start: 2025-01-01T00:00:00+01:00
consumption:
energy: { constant_cost: 95.0 }
injection:
energy: { constant_cost: -30.0 }
fixed:
subscription: { period: P1Y, constant_cost: 60.0 }
```
## Dynamic price
[Section titled “Dynamic price”](#dynamic-price)
110% of the day-ahead price plus 1.2 c/kWh and 1.5 c/kWh for green certificates. For injection, 90% of the day-ahead price minus a fee of 0.5 c/kWh. And 4.50 € a month.
```yaml
region: be_flanders
connection_type: electricity
customer_type: residential
distributor_key: fluvius_west
supplier:
- start: 2025-01-01T00:00:00+01:00
consumption:
energy:
constant_cost: 12.0
variable_costs:
- { index: belpex, scalar: 1.1 }
green_certificates: { constant_cost: 15.0 }
injection:
energy:
constant_cost: 5.0
variable_costs:
- { index: belpex, scalar: -0.9 }
fixed:
subscription: { period: P1M, constant_cost: 4.5 }
```
The index `belpex` has to be [registered](/docs/energy-cost/python/#indexes) before this contract is read. For injection, the fee is positive, paid by the customer, and the day-ahead share negative, received.
## Peak and off-peak
[Section titled “Peak and off-peak”](#peak-and-off-peak)
15 c/kWh on weekdays from 7:00 to 22:00, 9 c/kWh at all other times:
```yaml
region: be_flanders
connection_type: electricity
customer_type: residential
distributor_key: fluvius_antwerpen
supplier:
- start: 2025-01-01T00:00:00+01:00
consumption:
energy:
schedule:
- when:
- days: [monday, tuesday, wednesday, thursday, friday]
start: "07:00"
end: "22:00"
formula: { constant_cost: 150.0 }
- formula: { constant_cost: 90.0 }
injection:
energy: { constant_cost: -25.0 }
```
## A price change
[Section titled “A price change”](#a-price-change)
The price drops from 11 to 9.5 c/kWh on 1 July. A new version of the same tariff:
```yaml
region: be_flanders
connection_type: electricity
customer_type: residential
distributor_key: fluvius_imewo
supplier:
- start: 2025-01-01T00:00:00+01:00
consumption: { constant_cost: 110.0 }
- start: 2025-07-01T00:00:00+02:00
consumption: { constant_cost: 95.0 }
```
For a change of supplier instead, use a [contract history](/docs/energy-cost/contracts/#a-contract-that-changes).
## Outside Flanders
[Section titled “Outside Flanders”](#outside-flanders)
Without built-in data, write every part yourself:
```yaml
timezone: Europe/Amsterdam
supplier:
- start: 2025-01-01T00:00:00+01:00
consumption:
energy: { constant_cost: 100.0 }
distributor:
- start: 2025-01-01T00:00:00+01:00
consumption:
transport: { constant_cost: 40.0 }
fixed:
connection: { period: P1Y, constant_cost: 250.0 }
fees:
- start: 2025-01-01T00:00:00+01:00
consumption:
energy_tax: { constant_cost: 101.0 }
taxes:
- start: 2025-01-01T00:00:00+01:00
default: 0.21
```
The amounts are made up, to show the shape.
# Using it from Python
> Calculate a bill with energy-cost in your own code.
```sh
pip install energy-cost
```
energy-cost needs Python 3.11 or newer.
## Calculating a bill
[Section titled “Calculating a bill”](#calculating-a-bill)
```python
import pandas as pd
from energy_cost import Contract, Meter, PowerDirection, TimeseriesFrame
contract = Contract.from_yaml("contract.yml")
timestamps = pd.date_range("2025-01-01", "2025-04-01", freq="15min", inclusive="left", tz="Europe/Brussels")
consumption = Meter(measurements=TimeseriesFrame({"timestamp": timestamps, "value": 0.00025}))
injection = Meter(
direction=PowerDirection.INJECTION,
measurements=TimeseriesFrame({"timestamp": timestamps, "value": 0.0001}),
)
bill = contract.apply(consumption, injection=injection)
print(bill[("total", "total", "total")])
```
For a [contract history](/docs/energy-cost/contracts/#a-contract-that-changes), use `ContractHistory.from_yaml` instead; it works the same way.
## Meters
[Section titled “Meters”](#meters)
A `Meter` holds the measurements of one direction:
| Field | What it is |
| -------------- | -------------------------------------------------------------------------------------------------------------- |
| `direction` | `PowerDirection.CONSUMPTION`, the default, or `PowerDirection.INJECTION` |
| `type` | `MeterType.SINGLE_RATE`, the default, or `MeterType.NIGHT_ONLY` |
| `measurements` | A `TimeseriesFrame` with a `timestamp`, the start of each interval, and a `value`, the energy in MWh during it |
| `capacity` | Optional: the peaks, in MW, if you have them; otherwise they are worked out from the measurements |
`TimeseriesFrame` is a pandas DataFrame that also knows its resolution: from the first two timestamps, or given as `TimeseriesFrame(..., resolution=...)`.
* Measurements must cover every interval of the bill. A gap, or a missing value, makes that period’s total empty.
* For Flemish electricity, measurements must be per quarter-hour or finer, for the [capacity tariff](/docs/energy-cost/taxes-and-capacity/#capacity).
## The bill
[Section titled “The bill”](#the-bill)
`apply` takes these options:
| Option | What it is |
| ------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `injection` | The injection meter, if any |
| `start`, `end` | The period to bill. That of the measurements if left out. |
| `output_resolution` | The period of each row: a `datetime.timedelta`, or an `isodate.Duration` for months and years. A month if left out. |
The result is a pandas DataFrame with one row per period. Its `timestamp` column holds each period’s start, in the contract’s time zone. The other columns have three levels: the part of the bill, the [cost group](/docs/energy-cost/tariffs/#cost-groups) and the [cost type](/docs/energy-cost/tariffs/#cost-types). Each level has a `total`:
| Column | What it is |
| --------------------------------------- | ------------------------------ |
| `("supplier", "consumption", "energy")` | One cost |
| `("supplier", "consumption", "total")` | A cost group |
| `("supplier", "total", "total")` | A part of the bill |
| `("taxes", "total", "total")` | All taxes |
| `("total", "total", "total")` | The whole bill, taxes included |
## Indexes
[Section titled “Indexes”](#indexes)
A tariff that [follows an index](/docs/energy-cost/tariffs/#price-per-mwh) needs that index registered under its name before the tariff is read. For the Belgian day-ahead price from ENTSO-E:
```python
from energy_cost.index import CachedIndex, EntsoeDayAheadIndex, Index
Index.register("belpex", CachedIndex(EntsoeDayAheadIndex("BE", api_key="YOUR_ENTSOE_KEY"), file_name="belpex"))
```
`CachedIndex` keeps fetched prices on disk, under `~/.cache/energy_cost`. Your own prices work too, in €/MWh, with `timestamp` and `value` columns: `CSVIndex("prices.csv")` or `DataFrameIndex(df)`.
## Suppliers
[Section titled “Suppliers”](#suppliers)
To refer to a supplier’s product with `supplier_key` and `product_key` in a contract, register it first:
```python
from energy_cost import Supplier, Tariff
Supplier.register("my_supplier", Supplier(products={"fixed": Tariff.from_yaml("fixed.yml")}))
```
## Separate sets of names
[Section titled “Separate sets of names”](#separate-sets-of-names)
Indexes and suppliers are registered in the active `Catalog`. To keep separate sets, for example per customer, create a `Catalog` and use it within `with catalog.active():`.
## Prices of a tariff
[Section titled “Prices of a tariff”](#prices-of-a-tariff)
`Tariff.get_values` gives the prices of a tariff over time, in €/MWh, such as for a price chart:
```python
from datetime import datetime, timedelta, timezone
from energy_cost import CostGroup, Tariff
tariff = Tariff.from_yaml("tariff.yml")
prices = tariff.get_values(
datetime(2025, 1, 1, tzinfo=timezone.utc),
datetime(2025, 1, 2, tzinfo=timezone.utc),
timedelta(minutes=15),
cost_group=CostGroup.CONSUMPTION,
)
```
This is what Cofy’s [`energy_cost` source](/docs/sources/energy-cost/) serves.
# Tariffs
> How to write an energy-cost tariff, from a fixed price to time-of-use, tiers and dynamic prices.
A tariff says what each part of the energy costs over time. The supplier, the distributor and the fees in a [contract](/docs/energy-cost/contracts/) are all tariffs, and so is the `tariff` of Cofy’s [`energy_cost` source](/docs/sources/energy-cost/).
## Versions
[Section titled “Versions”](#versions)
A tariff is a list of **versions**. Each version starts at its `start` and runs until the next one starts; the last one runs on indefinitely. A price change is a new version:
```yaml
- start: 2025-01-01T00:00:00+01:00
consumption: { constant_cost: 95.0 }
- start: 2025-07-01T00:00:00+02:00
consumption: { constant_cost: 89.0 }
```
A version has no `end`; writing one is an error. Before the first version, the tariff has no price, so start your tariff no later than the meter data it’s used with.
## Cost groups
[Section titled “Cost groups”](#cost-groups)
Each version groups its costs by what they’re charged on:
| Group | Charged on |
| ------------- | ------------------------------------------------------------------------------------- |
| `consumption` | The energy taken from the grid |
| `injection` | The energy put on the grid |
| `capacity` | The energy taken from the grid, or its peak with [`capacity_based`](#capacity-prices) |
| `fixed` | Nothing: costs that don’t depend on use, such as a yearly fee |
The groups are labels for the bill: every group except `injection` is charged on the consumption meter, and a plain price per MWh under `capacity` is still per MWh. What makes a cost depend on the peak or on time is its [formula](#formulas).
## Cost types
[Section titled “Cost types”](#cost-types)
Within a group, you can name each cost, and the bill shows each name separately:
```yaml
- start: 2025-01-01T00:00:00+01:00
consumption:
energy: { constant_cost: 95.0 }
green_certificates: { constant_cost: 15.0 }
fixed:
subscription: { period: P1Y, constant_cost: 60.0 }
```
A group with a single formula and no names, such as `consumption: { constant_cost: 95.0 }`, is the same as naming it `total`. `total` is otherwise reserved: the bill adds it up for each group.
## Formulas
[Section titled “Formulas”](#formulas)
Each cost is a formula. energy-cost recognises the kind of formula from its fields:
| Formula | Recognised by | Use it for |
| --------------------------------------- | --------------------------------- | ------------------------------------------------- |
| [Price](#price-per-mwh) | `constant_cost`, `variable_costs` | A price per MWh, fixed or following an index |
| [Periodic](#fixed-amounts) | `period` and `constant_cost` | A fixed amount per day, month or year |
| [Per unit per period](#capacity-prices) | `kind: unit_periodic` | A price per MW per year, for capacity |
| [Schedule](#time-of-use) | `schedule` | Different prices at different times |
| [Tiers](#tiers) | `bands` | Prices that change with the amount used |
| [Lowest or highest](#lowest-or-highest) | `minimum`, `maximum` | Whichever of several formulas costs least or most |
| [By meter type](#by-meter-type) | `by_meter_type` | Different prices for different meters |
Formulas nest: the formula inside a schedule, a tier or a lowest-or-highest can be any formula.
### Price per MWh
[Section titled “Price per MWh”](#price-per-mwh)
```yaml
energy: { constant_cost: 95.0 }
```
A price in €/MWh for each MWh. With `variable_costs`, the price follows one or more **indexes**, such as the day-ahead market price, each multiplied by a `scalar`:
```yaml
energy:
constant_cost: 12.0
variable_costs:
- index: belpex
scalar: 1.1
```
This is 12 €/MWh plus 110% of the `belpex` index at that moment. An index has to be known by name before the tariff is read. In Python you [register it yourself](/docs/energy-cost/python/#indexes); a Cofy API knows the indexes whoever runs it has registered.
### Fixed amounts
[Section titled “Fixed amounts”](#fixed-amounts)
```yaml
subscription: { period: P1Y, constant_cost: 60.0 }
```
An amount in € per `period`, whatever is used. The period is an ISO 8601 duration: `P1D`, `P1M` or `P1Y`. The amount is spread evenly over the time it covers, so a yearly fee shows up as a share of it in each month.
### Time of use
[Section titled “Time of use”](#time-of-use)
A `schedule` is a list of entries, each a `formula` with the moments it applies to. At every moment the first matching entry applies, so end with an entry without `when` to cover all other times:
```yaml
energy:
schedule:
- when:
- days: [monday, tuesday, wednesday, thursday, friday]
start: "07:00"
end: "22:00"
formula: { constant_cost: 150.0 }
- formula: { constant_cost: 90.0 }
```
Each `when` is a list of moments; an entry applies when any of them matches.
| Field | What it is |
| ------- | ----------------------------------------------------------------- |
| `days` | The days it applies, `monday` to `sunday`. Every day if left out. |
| `start` | From this time, included. Midnight if left out. |
| `end` | Until this time, not included. Midnight if left out. |
Times are local, in the contract’s time zone. A moment can’t cross midnight: for 22:00 to 07:00, write two moments, `start: "22:00"` and `end: "07:00"`.
Caution
Always put times in quotes. YAML reads an unquoted `22:00` as a number of minutes, which silently becomes the wrong time.
### Tiers
[Section titled “Tiers”](#tiers)
`bands` give a different price depending on how much is used in a `band_period`. Each band covers use up to `up_to` MWh, from low to high; leave `up_to` out of the last one:
```yaml
excise:
band_period: P1Y
bands:
- up_to: 3
formula: { constant_cost: 50.0 }
- up_to: 20
formula: { constant_cost: 45.0 }
- formula: { constant_cost: 40.0 }
```
| Field | What it is |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `bands` | The bands, each an `up_to` in MWh and a `formula`. Required. |
| `band_period` | The period use is counted over, such as `P1Y`. Each measurement on its own if left out. |
| `mode` | `progressive`, the default: each band’s price applies to the use within that band, like tax brackets. `banded`: the band that the total use falls in prices all of it. |
When only part of a `band_period` is billed, its use is extrapolated to the whole period to find the band.
### Lowest or highest
[Section titled “Lowest or highest”](#lowest-or-highest)
```yaml
capacity:
total:
period: P1M
minimum:
- { constant_cost: 60.0 }
- { capacity_based: true, kind: unit_periodic, period: P1Y, constant_cost: 50000.0 }
```
For each `period`, applies whichever formula in the list gives the lowest total cost; `maximum` takes the highest. If two cost the same, the first wins. The two can be combined, for a cost with a floor and a ceiling.
### By meter type
[Section titled “By meter type”](#by-meter-type)
```yaml
energy:
by_meter_type:
night_only: { constant_cost: 70.0 }
default: { constant_cost: 95.0 }
```
Picks a formula by the type of meter: `single_rate` or `night_only`, with `default` for any other.
### Capacity prices
[Section titled “Capacity prices”](#capacity-prices)
Any formula can be charged on the peak instead of the energy, by adding `capacity_based: true`. It is then applied to the highest power, in MW, per billing period of the contract’s [capacity rule](/docs/energy-cost/taxes-and-capacity/#capacity). A capacity price is usually € per MW per year, which is the `unit_periodic` formula; it needs its `kind` written out:
```yaml
capacity:
total:
capacity_based: true
kind: unit_periodic
period: P1Y
constant_cost: 50000.0
```
That’s 50 € per kW per year, charged each billing period of the capacity rule on that period’s peak; in Flanders, per month. Without `kind: unit_periodic`, this would be read as a fixed amount per year.
## Writing a formula’s kind
[Section titled “Writing a formula’s kind”](#writing-a-formulas-kind)
energy-cost recognises a formula from its fields. If a formula has fields of more than one kind, it checks `bands` first, then `minimum`, `maximum`, `by_meter_type`, `period`, `schedule`, and finally `constant_cost` and `variable_costs`. To be explicit, add `kind`: `index`, `periodic`, `unit_periodic`, `scheduled`, `tiered`, `minimum`, `maximum` or `meter_type`.
# Taxes and capacity
> VAT rates on each part of the bill, and how the peak for a capacity tariff is found.
Both are filled in from the [built-in data](/docs/energy-cost/built-in-data/) of a region, so you only write them for a region energy-cost doesn’t know, or to change them.
## Taxes
[Section titled “Taxes”](#taxes)
`taxes` is a list of versions, like a [tariff](/docs/energy-cost/tariffs/#versions). Unlike a tariff version, a tax version may have an `end`.
| Field | What it is |
| --------- | ------------------------------------------------------------------------- |
| `start` | When the rates start. Required. |
| `end` | When they stop. Optional. |
| `default` | The rate for everything no other rate covers, as a fraction: 6% is `0.06` |
| `rates` | Other rates, each a `rate` and the `columns` of the bill it applies to |
A column is three parts: the part of the bill (`supplier`, `distributor`, `fees`), the [cost group](/docs/energy-cost/tariffs/#cost-groups) and the [cost type](/docs/energy-cost/tariffs/#cost-types). `*` stands for any. The built-in Belgian electricity taxes:
```yaml
taxes:
- start: 2022-01-01T00:00:00+01:00
default: 0.06
rates:
- rate: 0.0
columns:
- [fees, fixed, energy_fund]
- ["*", injection, "*"]
```
That’s 6% on everything, except nothing on the energy fund and on anything paid or received for injection. When several columns match a cost, the most specific one wins, with the cost type counting most.
The bill shows taxes as a single total.
## Capacity
[Section titled “Capacity”](#capacity)
Some distributors charge for the highest power a connection draws, its **peak**, instead of only for the energy. A `capacity_rule` says how that peak is measured:
| Field | What it is |
| -------------------- | ----------------------------------------------------------------------------------- |
| `measurement_period` | The period power is averaged over, such as `PT15M` |
| `billing_period` | The period whose highest value is the peak, such as `P1M` |
| `window_periods` | Optional: average the peak over this many billing periods, the current one included |
The built-in Flemish rule is the Flemish capacity tariff: the average power per quarter-hour, the highest of each month, averaged over the last twelve months.
```yaml
capacity_rule:
measurement_period: PT15M
billing_period: P1M
window_periods: 12
```
Formulas marked [`capacity_based`](/docs/energy-cost/tariffs/#capacity-prices) are charged on this peak. Meter data must be at least as fine as the `measurement_period`, so for Flanders every quarter-hour. For a full twelve-month average, pass a year of earlier data and set the bill’s start to the period you want billed.
If you already have the peaks, for example from a meter that measures them, you can give them to the meter directly and the rule’s averaging is skipped.
# Build and extend
> Where to start when you add to Cofy or build on its parts.
Cofy is open source and built from separate packages, so you can extend it, or take only the parts you need. The details live next to the code, in each package’s README; this page tells you where to look.
| Package | What it is |
| -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| [packages/api](https://github.com/EnergieID/cofy-api/tree/main/packages/api) | `cofy-api`: the Cofy API itself, its modules, sources and formats |
| [packages/management-api](https://github.com/EnergieID/cofy-api/tree/main/packages/management-api) | Stores community settings, and who may change them |
| [packages/runner](https://github.com/EnergieID/cofy-api/tree/main/packages/runner) | Serves each community’s settings as its own Cofy API |
| [packages/frontend-sdk](https://github.com/EnergieID/cofy-api/tree/main/packages/frontend-sdk) | A typed TypeScript client and state stores for the management API |
| [packages/web-components](https://github.com/EnergieID/cofy-api/tree/main/packages/web-components) | The building blocks of the console: generated forms, a YAML editor, lists and theming |
| [apps/management-web](https://github.com/EnergieID/cofy-api/tree/main/apps/management-web) | The console, built from the two above |
| [energy-cost](https://github.com/EnergieID/energy-cost) | The library that calculates energy costs; see [energy cost](/docs/energy-cost/) |
## A source or module of your own
[Section titled “A source or module of your own”](#a-source-or-module-of-your-own)
Every source belongs to a family, such as prices or production, and fits wherever that family does. A new source subclasses its family and fetches its data; once its package is installed, it shows up in settings files and in the console’s forms. [packages/api](https://github.com/EnergieID/cofy-api/tree/main/packages/api#sources-and-families) shows how, and how a separate package registers its types with an entry point.
## Your own console, or your own branding
[Section titled “Your own console, or your own branding”](#your-own-console-or-your-own-branding)
The console takes your brand colour, language and icons without code changes. [apps/management-web](https://github.com/EnergieID/cofy-api/tree/main/apps/management-web#customising) describes how, and how to add a page.
## Contributing
[Section titled “Contributing”](#contributing)
The [repository README](https://github.com/EnergieID/cofy-api#quick-start-this-repository) sets up a development environment and runs the demos. Questions, ideas and bug reports are welcome as [GitHub issues](https://github.com/EnergieID/cofy-api/issues).
# Modules
> What each module does, and what every module has in common.
A [module](/docs/concepts/#module) is one thing a community’s API can do. A community switches on only the modules it needs, and can switch on the same module more than once, for example a tariff for consumption and another for injection.
| Module | What it does | Takes a source |
| --------------------------------------------- | ----------------------------------------------------------------------------------------- | --------------------------- |
| [`members`](/docs/modules/members/) | Lists the community’s members, verifies their activation codes and serves their contracts | Yes, a member source |
| [`tariff`](/docs/modules/tariff/) | Serves energy prices as a time series | Yes, a price source |
| [`production`](/docs/modules/production/) | Serves energy production as a time series | Yes, a production source |
| [`simultaneity`](/docs/modules/simultaneity/) | Serves how much production is consumed at the same time | Yes, a ratio source |
| [`directive`](/docs/modules/directive/) | Serves a five-step signal, from `--` to `++`, telling members when to use energy | Yes, a directive source |
| [`billing`](/docs/modules/billing/) | Calculates energy costs from meter data and a contract | No |
| [`timeseries`](/docs/modules/timeseries/) | Serves any time series, such as a solar forecast | Yes, any time series source |
## Settings every module has
[Section titled “Settings every module has”](#settings-every-module-has)
| Field | What it is |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | The module, such as `tariff` |
| `name` | Lowercase letters, digits, `-` and `_`; `default` if left out. It is part of the module’s address, so it must be unique among modules of the same type. |
| `display_name` | A name for people; the `name` if left out |
| `description` | What this module is for; the module’s own description if left out |
In the console, these are the fields at the top of a module’s form. The examples in these docs each show one module, ready to paste into the console’s [YAML view](/docs/console/modules/):
```yaml
type: tariff
name: entsoe
display_name: Day-ahead prices
source:
type: entsoe_day_ahead
country_code: BE
api_key: { type: secret, name: entsoe_key }
```
In a [settings file](/docs/self-host/settings-file/), each module is an entry of the `modules` list:
```yaml
modules:
- type: tariff
name: entsoe
# …
```
## Where a module is served
[Section titled “Where a module is served”](#where-a-module-is-served)
Each module is served at `/{type}/{name}/{version}`, for example `/tariff/entsoe/v1`. The version is `v1` for every module except billing, which is at `v2`.
Every Cofy API also serves interactive API documentation at `/docs`, its OpenAPI description at `/openapi.json`, and a health check at `/health`.
## Time series modules
[Section titled “Time series modules”](#time-series-modules)
The `tariff`, `production`, `simultaneity`, `directive` and `timeseries` modules all serve a time series. How to ask them for a period, the formats they serve and how they cache are described once, on the [time series](/docs/modules/timeseries/) page.
# Billing
> Calculate energy costs from meter data and a contract.
The `billing` module calculates what a connection’s energy costs, from its meter data and its contract, with [energy-cost](/docs/energy-cost/). Both are sent with each request, so the module itself has nothing to configure.
Install: `pip install "cofy-api[billing]"`
## Settings
[Section titled “Settings”](#settings)
Only the [settings every module has](/docs/modules/#settings-every-module-has):
```yaml
type: billing
```
## Calculating a bill
[Section titled “Calculating a bill”](#calculating-a-bill)
`POST /billing/{name}/v2`, with a body like this:
```json
{
"start": "2025-01-01T00:00:00+01:00",
"end": "2025-02-01T00:00:00+01:00",
"resolution": "P1M",
"consumption": {
"type": "single_rate",
"measurements": {
"resolution": "PT15M",
"values": [
{ "timestamp": "2025-01-01T00:00:00+01:00", "value": 0.25 },
{ "timestamp": "2025-01-01T00:15:00+01:00", "value": 0.31 }
]
}
},
"contract": {
"region": "be_flanders",
"connection_type": "electricity",
"customer_type": "residential",
"distributor_key": "fluvius_imewo",
"supplier": [{ "start": "2025-01-01T00:00:00+01:00", "consumption": { "constant_cost": 90.0 } }]
}
}
```
| Field | What it is |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `start`, `end` | The period to bill. That of the measurements if left out. |
| `resolution` | The period of each row of the bill: `P1M` if left out, or `P1D`, `P1Y`. |
| `consumption` | The consumption meter. Required. |
| `injection` | The injection meter, if any, in the same shape |
| `contract` | A [contract](/docs/energy-cost/contracts/), or a list of them for a [contract that changes](/docs/energy-cost/contracts/#a-contract-that-changes), written as JSON |
A meter has:
| Field | What it is |
| -------------- | ---------------------------------------------------------------------------------------------------------------- |
| `type` | `single_rate`, the default, or `night_only` |
| `measurements` | The `values`, each a `timestamp` and the energy in **kWh** in the interval starting then, and their `resolution` |
| `capacity` | Optional: the peaks, in **W**, in the same shape, if you have them |
Caution
Always give the `resolution` of the measurements, such as `PT15M`: it is `P1M` if left out. For Flemish electricity, measurements must be per quarter-hour or finer, for the [capacity tariff](/docs/energy-cost/taxes-and-capacity/#capacity).
The billing module takes kWh and W, while energy-cost itself works in MWh and MW: the module converts them. Prices in the contract stay in €/MWh.
Measurements must cover the whole period: a period with a missing value gets no total.
## The answer
[Section titled “The answer”](#the-answer)
For January 2025, with 0.25 kWh every quarter-hour:
```json
{
"metadata": { "start": "2025-01-01T00:00:00+01:00", "end": "2025-02-01T00:00:00+01:00", "resolution": "P1M" },
"data": [
{
"timestamp": "2025-01-01T00:00:00+01:00",
"supplier": {
"consumption": { "total": 66.96 },
"total": { "total": 66.96 }
},
"distributor": {
"capacity": { "total": 11.304858817123286 },
"consumption": {
"levies": 1.3409856,
"public_service_fee": 24.1724856,
"transmission": 18.3104352,
"total": 43.8239064
},
"fixed": { "data_management": 1.487150684931507, "total": 1.487150684931507 },
"total": { "total": 56.615915902054795 }
},
"fees": {
"consumption": { "energy_contribution": 1.4330184, "excise": 35.32512, "total": 36.7581384 },
"total": { "total": 36.7581384 }
},
"taxes": { "total": { "total": 9.620043258123287 } },
"total": { "total": { "total": 169.95409756017807 } }
}
]
}
```
Each part of the bill is broken down by [cost group](/docs/energy-cost/tariffs/#cost-groups) and [cost type](/docs/energy-cost/tariffs/#cost-types), in €, with a `total` at each level. Amounts aren’t rounded, and each row’s `timestamp` is the start of its period, in the contract’s time zone. A part that doesn’t apply is left out. An invalid contract or meter gives a 400 with the reason.
## Contracts from the members module
[Section titled “Contracts from the members module”](#contracts-from-the-members-module)
The [members module](/docs/modules/members/) serves each member’s contract history in this same contract form, so an app can fetch a contract there and send it here.
# Directive
> Tell members when to use more or less energy, with a five-step signal.
The `directive` module serves a [time series](/docs/modules/timeseries/) of steps telling members when to use energy:
| Step | Meaning |
| ---- | -------------------------------- |
| `++` | A very good moment to use energy |
| `+` | A good moment |
| `0` | Neutral |
| `-` | Better to wait |
| `--` | Use as little as possible |
A directive turns a number into these steps, such as the day-ahead price, a solar forecast or the community’s [simultaneity](/docs/modules/simultaneity/).
Install: `pip install "cofy-api[directive]"`, plus the extra of the source you use.
## Settings
[Section titled “Settings”](#settings)
| Field | What it is |
| --------- | -------------------------------------------------------------------------- |
| `source` | A directive source, below. Required. |
| `formats` | The formats to offer: `directive` and `csv`. Only `directive` if left out. |
And the [settings every module has](/docs/modules/#settings-every-module-has).
The `directive` format is JSON, like other time series, with a step as each value and `directive` as the unit:
```json
{
"metadata": { "start": "…", "end": "…", "format": "json", "resolution": "PT1H", "unit": "directive" },
"data": [{ "timestamp": "2026-01-01T12:00:00Z", "value": "++" }]
}
```
## Fixed boundaries: the `directive` source
[Section titled “Fixed boundaries: the directive source”](#fixed-boundaries-the-directive-source)
The `directive` source compares each value of a number series with four boundaries, from low to high:
| Value | Step |
| ------------------------ | ---- |
| up to the first boundary | `--` |
| above the first | `-` |
| above the second | `0` |
| above the third | `+` |
| above the fourth | `++` |
| Field | What it is |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `source` | The number series: any price, production, ratio or net volume source, or a forecast from [Elia](/docs/sources/elia/) or [NED](/docs/sources/ned/). Required. |
| `boundaries` | Four numbers, from low to high, in the unit of the source. Required. |
| `reverse` | Turn the steps around, so that high values give `--`. Use it for prices and simultaneity, where a high value means a bad moment. `false` if left out. |
| `cache` | How to [cache](/docs/sources/#caching) the result. Optional. |
It supports the resolutions of its source.
## Boundaries that change: the `dynamic_boundary_directive` source
[Section titled “Boundaries that change: the dynamic_boundary_directive source”](#boundaries-that-change-the-dynamic_boundary_directive-source)
The `dynamic_boundary_directive` source works the same way, but takes its four boundaries from a second series, so they can change through the day.
| Field | What it is |
| ----------------- | ------------------------------------------------------------ |
| `signal_source` | The number series, as for the `directive` source. Required. |
| `boundary_source` | A series of four boundaries for each moment. Required. |
| `reverse` | As for the `directive` source. |
| `cache` | How to [cache](/docs/sources/#caching) the result. Optional. |
Only moments for which both series have a value get a step. For now the only boundary source is [`demo_boundary`](/docs/sources/demo/).
## Examples
[Section titled “Examples”](#examples)
Cheap day-ahead prices are a good moment to use energy, so the steps are reversed:
```yaml
type: directive
name: prices
source:
type: directive
source:
type: entsoe_day_ahead
country_code: BE
api_key: { type: secret, name: entsoe_key }
boundaries: [0, 10, 25, 50]
reverse: true
```
Sunny hours in Antwerp, from Elia’s solar forecast as a percentage of the region’s solar capacity:
```yaml
type: directive
name: sun
source:
type: directive
source: { type: elia_solar_forecast, region: Antwerpen }
boundaries: [0, 3, 12, 40]
```
# Members
> List a community's members, verify their activation codes and serve their contracts.
The `members` module serves a community’s members: who they are, their addresses and the energy contracts at each address. An app can use it to let a member sign in with an activation code.
Install: `pip install "cofy-api[members]"`, plus the extra of the source you use.
## Settings
[Section titled “Settings”](#settings)
| Field | What it is |
| -------- | -------------------------------------- |
| `source` | Where the members come from. Required. |
And the [settings every module has](/docs/modules/#settings-every-module-has).
For now the only member source you can configure is [`demo_members`](/docs/sources/demo/), with example members.
## Endpoints
[Section titled “Endpoints”](#endpoints)
| Endpoint | What it does |
| ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `GET /members/{name}/v1/` | Lists all members |
| `GET /members/{name}/v1/{member_id}` | One member, or 404 |
| `POST /members/{name}/v1/verify` | Finds the member with the `activation_code` in the body, or 404 |
| `GET /members/{name}/v1/{member_id}/contracts/{ean}` | The contract history of one connection, in [energy-cost](/docs/energy-cost/) form, ready for the [billing module](/docs/modules/billing/) |
The contracts endpoint takes an optional `meter_type`, `day_night_07` or `day_night_06`, for connections with a day and night meter.
## A member
[Section titled “A member”](#a-member)
```json
{
"id": "M001",
"activation_code": "…",
"addresses": [
{
"contracts": [
{
"ean": "541234567890123456",
"customer_type": "residential",
"connection_type": "electricity",
"supplier": { "name": "…", "id": "…" },
"product": { "name": "…", "id": "…" },
"distributor": { "name": "…", "id": "…" },
"region": { "name": "…", "id": "…" },
"start_date": "2024-01-01",
"end_date": null,
"last_invoice_date": null,
"is_green": true
}
]
}
]
}
```
| Field | What it is |
| ----------------- | ----------------------------------------------- |
| `customer_type` | `residential`, `non_residential` or `protected` |
| `connection_type` | `electricity` or `gas` |
| `is_green` | Whether the contract is for green energy |
## Example
[Section titled “Example”](#example)
```yaml
type: members
name: demo
source: { type: demo_members }
```
# Production
> Serve local energy production as a time series.
The `production` module serves energy produced, in kWh, as a [time series](/docs/modules/timeseries/): what a community’s wind turbine or solar panels produced.
Install: `pip install "cofy-api[production]"`, plus the extra of the source you use.
## Settings
[Section titled “Settings”](#settings)
| Field | What it is |
| --------- | ------------------------------------------ |
| `source` | Where the production comes from. Required. |
| `formats` | `json` and `csv`, the defaults. |
And the [settings every module has](/docs/modules/#settings-every-module-has).
### Sources
[Section titled “Sources”](#sources)
| Source | Production |
| ------------------------------------------------ | --------------------------------------------- |
| [`energyid_production`](/docs/sources/energyid/) | The production of a meter, from EnergyID |
| [`demo_production`](/docs/sources/demo/) | Generated solar production, to try things out |
A forecast of production, such as [Elia’s](/docs/sources/elia/) or [NED’s](/docs/sources/ned/), is served by the [`timeseries`](/docs/modules/timeseries/) module instead.
## Asking for production
[Section titled “Asking for production”](#asking-for-production)
`GET /production/{name}/v1`, with the [usual time series parameters](/docs/modules/timeseries/#asking-for-a-period). Without a `resolution`, you get the source’s finest one: for EnergyID, steps of 5 minutes.
## Example
[Section titled “Example”](#example)
```yaml
type: production
name: wind
source:
type: energyid_production
api_key: { type: secret, name: energyid_key }
record_id: your-record-id
```
# Simultaneity
> Serve how much of the local production is consumed at the same time.
The `simultaneity` module serves, as a [time series](/docs/modules/timeseries/), how well a community’s energy production and consumption match up. 100% means that, at that moment, consumption matches production. Below 100%, members use less than is produced; above 100%, they need more than is produced.
Install: `pip install "cofy-api[simultaneity]"`, plus the extra of the source you use.
## Settings
[Section titled “Settings”](#settings)
| Field | What it is |
| --------- | ------------------------------------------ |
| `source` | Where the percentages come from. Required. |
| `formats` | `json` and `csv`, the defaults. |
And the [settings every module has](/docs/modules/#settings-every-module-has).
### Sources
[Section titled “Sources”](#sources)
| Source | Percentages |
| ----------------------------------- | -------------------------------------------------------------------- |
| `simultaneity` | Calculated from the net volume of the community’s connections, below |
| [`demo_ratio`](/docs/sources/demo/) | Generated percentages, to try things out |
## The simultaneity source
[Section titled “The simultaneity source”](#the-simultaneity-source)
The `simultaneity` source adds up the net volume of a set of connections: what each one takes from the grid, positive, or puts on it, negative. For every moment it divides all consumption by all production.
| Field | What it is |
| --------- | --------------------------------------------------------------------------------------- |
| `sources` | One or more net volume sources, such as [`acc_forecast`](/docs/sources/evo/). Required. |
| `cache` | How to [cache](/docs/sources/#caching) the result. Optional. |
* When there is neither consumption nor production, simultaneity is 100%.
* It supports the resolutions all its sources support, and its data stays valid as long as that of its shortest-lived source.
Because the result is a number, the simultaneity source also fits a [directive](/docs/modules/directive/), to tell members when to use energy.
## Example
[Section titled “Example”](#example)
```yaml
type: simultaneity
name: pilot
source:
type: simultaneity
sources:
- type: acc_forecast
ean: "541234567890123456"
credentials: { type: secret, name: evo_credentials }
cache: { max_age: PT15M }
```
# Tariff
> Serve energy prices as a time series.
The `tariff` module serves energy prices, in EUR/MWh, as a [time series](/docs/modules/timeseries/). Use it for the day-ahead market price, or for your own tariff.
Install: `pip install "cofy-api[tariff]"`, plus the extra of the source you use.
## Settings
[Section titled “Settings”](#settings)
| Field | What it is |
| --------- | --------------------------------------------------------------------------- |
| `source` | Where the prices come from. Required. |
| `formats` | The formats to offer: `json`, `csv` and `kiwatt`. JSON and CSV if left out. |
And the [settings every module has](/docs/modules/#settings-every-module-has).
### Sources
[Section titled “Sources”](#sources)
| Source | Prices |
| ------------------------------------------- | -------------------------------------------- |
| [`entsoe_day_ahead`](/docs/sources/entsoe/) | The day-ahead market price of a bidding zone |
| [`energy_cost`](/docs/sources/energy-cost/) | Your own tariff, fixed or following an index |
| [`demo_price`](/docs/sources/demo/) | Generated prices, to try things out |
## Asking for prices
[Section titled “Asking for prices”](#asking-for-prices)
`GET /tariff/{name}/v1`, with the [usual time series parameters](/docs/modules/timeseries/#asking-for-a-period). Unlike the other modules, a tariff looks ahead: without parameters you get the next three days, from the current quarter-hour, in steps of 15 minutes.
## The Kiwatt format
[Section titled “The Kiwatt format”](#the-kiwatt-format)
The `kiwatt` format serves prices in the shape Kiwatt expects:
```json
{
"generatedAtUTC": "…",
"periodStartUTC": "…",
"periodEndUTC": "…",
"source": "Cofy-API-Demo",
"unit": "EUR/MWh",
"resolution": "PT15M",
"prices": [{ "startUTC": "…", "value": 92.4 }]
}
```
Its one setting, `source`, is the name written into the answer.
## Example
[Section titled “Example”](#example)
Belgian day-ahead prices in the Kiwatt format, at `/tariff/kiwatt/v1`:
```yaml
type: tariff
name: kiwatt
source:
type: entsoe_day_ahead
country_code: BE
api_key: { type: secret, name: entsoe_key }
formats:
- type: kiwatt
```
To serve the same prices in JSON from a second module, without fetching them twice, make the source a [shared resource](/docs/sources/#sharing-a-source).
# Time series
> Serve any time series, and how every time series module is asked for data.
The `timeseries` module serves the data of any time series source as it is. Use it for data that has no module of its own, such as a solar forecast from [Elia](/docs/sources/elia/) or [NED](/docs/sources/ned/).
The [`tariff`](/docs/modules/tariff/), [`production`](/docs/modules/production/), [`simultaneity`](/docs/modules/simultaneity/) and [`directive`](/docs/modules/directive/) modules are time series modules too: they serve a more specific kind of series, and are asked for data the same way. This page describes that once, for all of them.
Install: `pip install "cofy-api[timeseries]"`, plus the extra of the source you use.
## Settings
[Section titled “Settings”](#settings)
| Field | What it is |
| --------- | --------------------------------- |
| `source` | Any time series source. Required. |
| `formats` | `json` and `csv`, the defaults. |
And the [settings every module has](/docs/modules/#settings-every-module-has).
## Example
[Section titled “Example”](#example)
Elia’s solar forecast for Antwerp, at `/timeseries/solar/v1`:
```yaml
type: timeseries
name: solar
source: { type: elia_solar_forecast, region: Antwerpen }
```
The unit is in the answer’s metadata.
## Asking for a period
[Section titled “Asking for a period”](#asking-for-a-period)
| Parameter | What it is |
| ------------ | --------------------------------------------------------------------------- |
| `start` | The first moment, in ISO 8601, such as `2026-01-01T00:00:00+01:00` |
| `end` | The moment after the last value |
| `limit` | How many values, instead of an `end` |
| `offset` | Moves both `start` and `end` by this many steps |
| `resolution` | The step between values, as an ISO 8601 duration, such as `PT15M` or `PT1H` |
* If you leave out `start`, it is a day ago; if you leave out both `end` and `limit`, the series runs until now. The [tariff module](/docs/modules/tariff/) looks ahead instead.
* If you send both `end` and `limit`, the later of the two ends wins.
* A time without a time zone is taken as UTC. Timestamps in the answer are always in UTC.
* The `resolution` must be one the source supports; each source page lists them. If a source supports only one, the parameter is left out of the API documentation.
* Some sources add parameters of their own, such as `country_code` for [ENTSO-E](/docs/sources/entsoe/).
## Formats
[Section titled “Formats”](#formats)
Every time series module serves its first format at its address, and every other format at the address followed by the format’s name. With the defaults:
* `/timeseries/solar/v1` serves JSON:
```json
{
"metadata": { "start": "…", "end": "…", "format": "json", "resolution": "PT1H", "unit": "%" },
"data": [{ "timestamp": "2026-01-01T12:00:00Z", "value": 42.1 }]
}
```
* `/timeseries/solar/v1.csv` serves CSV with the columns `timestamp,value`, and the same metadata in an `x-metadata` header.
A module’s `formats` setting lists the formats it offers, in order. Some modules have formats of their own, like the tariff module’s [`kiwatt`](/docs/modules/tariff/#the-kiwatt-format) and the directive module’s [`directive`](/docs/modules/directive/).
## Caching
[Section titled “Caching”](#caching)
When a source says how long its data stays valid, the answer carries a `Cache-Control: max-age` header, so clients and proxies can cache it too. How a source caches what it fetches is set on the source; see [caching](/docs/sources/#caching).
# The settings file
> Everything a community's settings file can hold.
A community’s settings describe its whole Cofy API: its modules, what they share, its credentials and who may call it. You write it yourself when you [run one community yourself](/docs/self-host/single-community/); in a hosted Cofy, the [console](/docs/console/) writes it for you.
```yaml
type: cofy_api
title: Our community
description: Prices and production for the members of our community.
secrets:
- name: entsoe_key
value: ${ENTSOE_API_KEY}
resources:
- type: source
name: day_ahead
value:
type: entsoe_day_ahead
api_key: { type: secret, name: entsoe_key }
modules:
- type: tariff
name: day-ahead
source: { type: resource, name: day_ahead }
- type: directive
name: prices
source:
type: directive
source: { type: resource, name: day_ahead }
boundaries: [0, 10, 25, 50]
reverse: true
auth:
type: token
tokens:
- name: website
hash: ${WEBSITE_TOKEN_HASH}
```
## Fields
[Section titled “Fields”](#fields)
| Field | What it is |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | Always `cofy_api`. Required. |
| `title` | The API’s name, shown in its API docs. `Cofy API` if left out. |
| `description` | What the API is for, shown in its API docs |
| `modules` | The [modules](/docs/modules/) it serves |
| `resources` | Values used in several places; see [resources](#resources) |
| `secrets` | Credentials; see [secrets](#secrets) |
| `auth` | Who may call it; see [tokens](#tokens). Anyone may, if left out. |
| `debug_mode` | Profiles every request, for finding out what makes one slow; see [debug mode](#debug-mode). `false` if left out. |
| `debug_dir` | Where debug mode keeps its profiles. A temporary directory if left out. |
| `revision` | A number that goes up with every change. The API reports it at `/health`, and in its version, such as `0.9.0+12`. In a hosted Cofy, the console keeps it for you. |
Every module, resource and source has a `type`, which says what it is and which other fields it takes. Each module’s and source’s page lists them. The examples on those pages show a single module: add it to the `modules` list as one entry, starting with `- type:`.
## Secrets
[Section titled “Secrets”](#secrets)
A [secret](/docs/concepts/#secret) holds a credential, such as an API key, under a name:
```yaml
secrets:
- name: entsoe_key
description: Our ENTSO-E API key
value: ${ENTSOE_API_KEY}
```
Every field that needs a credential refers to a secret instead of holding it: `{ type: secret, name: entsoe_key }`. That keeps credentials in one place, and out of the rest of the file.
To keep them out of the file entirely, the `app.py` of [run one community yourself](/docs/self-host/single-community/#3-load-it) fills in `${VARIABLES}` from the environment before reading the file. That is up to the code loading the file: Cofy itself reads only YAML.
## Resources
[Section titled “Resources”](#resources)
A [resource](/docs/concepts/#resource) is a value configured once, under a name, and referred to wherever one of its kind fits: `{ type: resource, name: day_ahead }`.
| `type` | What `value` holds | Fits wherever |
| --------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| `source` | A time series source | a source of the same kind fits |
| `member_source` | Where the members list comes from | the [members module](/docs/modules/members/) takes its source |
| `tariff` | A tariff, in [energy-cost’s format](/docs/energy-cost/tariffs/) | a tariff fits, as in the [energy-cost source](/docs/sources/energy-cost/#sharing-a-tariff) |
A source used as a resource is built once: every module using it shares one connection and one cache.
## Tokens
[Section titled “Tokens”](#tokens)
With `auth`, every request needs a token, except `/health`:
```yaml
auth:
type: token
tokens:
- name: website
description: Our community's website
hash: sha256:4c1d…
expires: 2027-01-01T00:00:00Z
```
| Field | What it is |
| ------------- | ----------------------------------------------------------------------------------------------- |
| `name` | Which token this is. Letters, digits, `-` and `_`; unique. |
| `description` | Who uses it. Optional. |
| `hash` | The hash of its key, so the key itself isn’t in the file |
| `key` | The key itself, instead of its hash. Simpler, but anyone who can read the file can then use it. |
| `expires` | When it stops working. Optional. |
Generate a strong key and its hash with:
```sh
python -c "from cofy.api import generate_key, hash_key; key = generate_key(); print(key, hash_key(key))"
```
An app sends its key in a header, `Authorization: Bearer `, or in the address, `?token=`.
## Debug mode
[Section titled “Debug mode”](#debug-mode)
With `debug_mode: true`, the API profiles every request, keeps the profiles of the last hundred, and adds a `/debug/{id}/profile` address to fetch one. It needs the `debug` extra: `pip install "cofy-api[debug]"`. It slows every request down, so only switch it on while looking into a problem.
## Tariffs that follow an index
[Section titled “Tariffs that follow an index”](#tariffs-that-follow-an-index)
An [energy-cost tariff](/docs/energy-cost/tariffs/#price-per-mwh) that follows an index, such as the day-ahead price, needs that index registered by name before the settings are read. Add it to `app.py`, before `CofyAPI.create`:
```python
from energy_cost.index import CachedIndex, EntsoeDayAheadIndex, Index
Index.register(
"Belpex15min",
CachedIndex(EntsoeDayAheadIndex("BE", api_key=os.environ["ENTSOE_API_KEY"]), file_name="belpex15min"),
)
```
See [indexes](/docs/energy-cost/python/#indexes) for the kinds of index there are. A hosted Cofy, run by the runner, can’t register indexes yet ([#257](https://github.com/EnergieID/cofy-api/issues/257)).
# Run one community yourself
> Install and run your own Cofy API for a single community.
Running your own Cofy API for one community is the simplest way to self-host. It needs no database and no console: you describe your community in a settings file, and Cofy serves it.
You need Python 3.11 or newer.
Start from the template
[cofy-api-template](https://github.com/EnergieID/cofy-api-template) is a repository ready to fork, with a Dockerfile to deploy it. This page shows how to start without it.
## 1. Install
[Section titled “1. Install”](#1-install)
Install Cofy with the extras of the modules and sources you use. For day-ahead prices from ENTSO-E:
```sh
pip install "cofy-api[tariff,entsoe]"
```
Each [module](/docs/modules/) and [source](/docs/sources/) page names its extra. `cofy-api[all]` installs everything.
## 2. Describe your community
[Section titled “2. Describe your community”](#2-describe-your-community)
Create a `settings.yaml`. This one serves the Belgian day-ahead prices, and lets in only apps with a token:
```yaml
type: cofy_api
title: Our community
secrets:
- name: entsoe_key
value: ${ENTSOE_API_KEY}
modules:
- type: tariff
name: day-ahead
source:
type: entsoe_day_ahead
country_code: BE
api_key: { type: secret, name: entsoe_key }
auth:
type: token
tokens:
- name: website
hash: ${WEBSITE_TOKEN_HASH}
```
The [settings file](/docs/self-host/settings-file/) page describes everything it can hold; each module’s and source’s page has examples to copy.
## 3. Load it
[Section titled “3. Load it”](#3-load-it)
Create an `app.py` next to it:
```python
import os
from pathlib import Path
import yaml
from cofy.api import CofyAPI
from cofy.modules.discovery import discover_all_types
# Make every installed module and source available to the settings.
discover_all_types()
# Fill in ${VARIABLES} from the environment, to keep credentials out of the file.
settings = yaml.safe_load(os.path.expandvars(Path(__file__).with_name("settings.yaml").read_text()))
app = CofyAPI.create(settings)
```
## 4. Run it
[Section titled “4. Run it”](#4-run-it)
Create a key for your app, and its hash:
```sh
python -c "from cofy.api import generate_key, hash_key; key = generate_key(); print(key, hash_key(key))"
```
Give the key to the app, and the hash to Cofy, with your ENTSO-E key:
```sh
export ENTSOE_API_KEY=your-entsoe-key
export WEBSITE_TOKEN_HASH=sha256:…
fastapi dev app.py
```
Your API is now at `http://127.0.0.1:8000`:
* the prices at `/tariff/day-ahead/v1?token=`;
* interactive API docs at `/docs?token=`;
* a health check, without a token, at `/health`.
`fastapi dev` restarts when you change a file. Changing `settings.yaml` doesn’t count: restart it yourself.
## Running it for real
[Section titled “Running it for real”](#running-it-for-real)
* Run it with `fastapi run app.py`, or any ASGI server, such as `uvicorn app:app --host 0.0.0.0 --port 8080`.
* Put it behind a proxy that adds HTTPS: tokens travel with every request.
* If the proxy serves it under a path, such as `/cofy/`, strip that path in the proxy and pass it to uvicorn as `--root-path /cofy`, so the API docs point to the right place.
* `/health` answers `"status": "ok"` without a token, for your monitoring.
## Or assemble it in Python
[Section titled “Or assemble it in Python”](#or-assemble-it-in-python)
Instead of a settings file, you can build the same API in code, which also lets you add [modules and sources of your own](/docs/extend/):
```python
from cofy.api import CofyAPI
from cofy.integrations.entsoe import EntsoeDayAheadTariffSource
from cofy.modules.tariff import TariffModule
app = CofyAPI()
app.register_module(TariffModule(source=EntsoeDayAheadTariffSource(api_key="YOUR_ENTSOE_KEY"), name="day-ahead"))
```
The repository has a worked example of each: [assembled in Python](https://github.com/EnergieID/cofy-api/tree/main/apps/demo_cofy_api) and [from a settings file](https://github.com/EnergieID/cofy-api/tree/main/apps/demo_cofy_api_settings).
# Sources
> Where modules get their data from, and how to configure, share and cache a source.
A [source](/docs/concepts/#source) is where a module gets its data from. Each source provides one kind of data, and each module only takes sources of the kind it serves.
## Which source fits which module
[Section titled “Which source fits which module”](#which-source-fits-which-module)
| Source | Data | Fits | Install |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | ---------------------------------- | -------------- |
| [`entsoe_day_ahead`](/docs/sources/entsoe/) | Day-ahead electricity prices | `tariff`, `directive` | `entsoe` |
| [`energy_cost`](/docs/sources/energy-cost/) | Prices from your own tariff | `tariff`, `directive` | `energy-cost` |
| [`energyid_production`](/docs/sources/energyid/) | Production of a meter | `production`, `directive` | `energyid` |
| [`acc_forecast`](/docs/sources/evo/) | Forecast net volume of a connection, from EvO | `simultaneity` source, `directive` | `acc` |
| [`elia_solar_forecast`](/docs/sources/elia/) | Solar forecast of a Belgian region | `timeseries`, `directive` | `elia` |
| [`ned_production_forecast`](/docs/sources/ned/) | Production forecast of Dutch installations | `timeseries`, `directive` | `ned` |
| [`simultaneity`](/docs/modules/simultaneity/#the-simultaneity-source) | Simultaneity, from net volumes | `simultaneity`, `directive` | `simultaneity` |
| [`directive`](/docs/modules/directive/#fixed-boundaries-the-directive-source), [`dynamic_boundary_directive`](/docs/modules/directive/#boundaries-that-change-the-dynamic_boundary_directive-source) | Directive steps | `directive` | `directive` |
| [`demo_*`](/docs/sources/demo/) | Generated data of every kind | every module | `demo` |
“Install” is the extra to add: `pip install "cofy-api[tariff,entsoe]"`, for example. Every source also fits the [`timeseries`](/docs/modules/timeseries/) module, which serves any series as it is.
## Credentials: secrets
[Section titled “Credentials: secrets”](#credentials-secrets)
A source that needs an API key or other credential takes a [secret](/docs/concepts/#secret), referenced by name:
```yaml
api_key: { type: secret, name: entsoe_key }
```
In the console, store the key in the community’s Secrets tab and pick it by name. In a settings file, list it under `secrets`:
```yaml
secrets:
- name: entsoe_key
description: Our ENTSO-E Transparency Platform key.
value: your-key
```
## Sharing a source
[Section titled “Sharing a source”](#sharing-a-source)
A source configured once as a [resource](/docs/concepts/#resource) can be used by several modules. It is fetched once, so they share one cache:
```yaml
resources:
- type: source
name: entsoe_be
value:
type: entsoe_day_ahead
country_code: BE
api_key: { type: secret, name: entsoe_key }
cache: {}
modules:
- type: tariff
name: entsoe
source: { type: resource, name: entsoe_be }
- type: directive
name: prices
source:
type: directive
source: { type: resource, name: entsoe_be }
boundaries: [0, 10, 25, 50]
reverse: true
```
## Caching
[Section titled “Caching”](#caching)
Without a `cache`, a source fetches its data again for every request. Give it a `cache` to keep what it fetched in memory:
| Field | What it is |
| ------------ | --------------------------------------------------------------------------------------------------------- |
| `max_age` | How long fetched data stays valid, as an ISO 8601 duration such as `PT15M`. The source’s own if left out. |
| `chunk_size` | The blocks of time fetched and kept together. A day, `P1D`, if left out. |
| `max_chunks` | How many blocks to keep, dropping the least recently used first. 1024 if left out. |
`cache: {}` uses all defaults. That only works for sources that say how long their data stays valid, such as ENTSO-E, Elia and NED; each source page says so. For the others, give a `max_age`.
If fetching fresh data fails while older data is still in the cache, the older data is served rather than an error.
# Demo sources
> Generated data of every kind, to try Cofy without any credentials.
The demo sources generate realistic data, so you can try every module without accounts or API keys.
Install: `pip install "cofy-api[demo]"`
| Source | Data | Fits |
| ----------------- | ----------------------------------------------------- | --------------------------------------- |
| `demo_price` | Prices, in EUR/MWh | `tariff`, `directive` |
| `demo_production` | Solar production, in kWh | `production`, `directive` |
| `demo_net_volume` | Net volume of a connection, in kWh | the `simultaneity` source, `directive` |
| `demo_ratio` | Simultaneity, in % | `simultaneity`, `directive` |
| `demo_boundary` | Four directive boundaries that change through the day | the `dynamic_boundary_directive` source |
| `demo_members` | Example members | `members` |
## Settings
[Section titled “Settings”](#settings)
Each series source has realistic defaults, and these settings:
| Field | What it is |
| --------- | -------------------------------------------------------------------------------------------------------------------------- |
| `pattern` | Your own values, spread evenly over each `period`. For `demo_boundary`, each value is a list of four boundaries. |
| `period` | How long the pattern takes before it repeats, as an ISO 8601 duration. A day, `P1D`, if left out. |
| `noise` | Random variation, as a fraction of each value, from 0 to 1. The same moment always gets the same variation. 0 if left out. |
| `cache` | How to [cache](/docs/sources/#caching) the data. Optional; give it a `max_age`. |
Patterns run in UTC, and a weekly pattern starts on a Monday. Any resolution works.
`demo_members` takes a list of `members` instead, with two example members by default.
## Examples
[Section titled “Examples”](#examples)
Prices that are low at night and high during the day:
```yaml
type: demo_price
pattern: [30, 30, 50, 70, 90, 110, 90, 50]
noise: 0.1
```
A directive between boundaries that change through the day:
```yaml
type: dynamic_boundary_directive
signal_source: { type: demo_price, noise: 0.1 }
boundary_source: { type: demo_boundary }
```
# Elia solar forecast
> Elia's solar production forecast of a Belgian region.
The `elia_solar_forecast` source provides Elia’s most recent solar production forecast for a Belgian region, from [Elia Open Data](https://opendata.elia.be/). It is a percentage of the region’s monitored solar capacity, so it shows how sunny it will be regardless of how much solar a region has.
Install: `pip install "cofy-api[elia]"`. No API key needed.
Fits: [`timeseries`](/docs/modules/timeseries/) and [`directive`](/docs/modules/directive/).
## Settings
[Section titled “Settings”](#settings)
| Field | What it is |
| -------- | -------------------------------------------------------------- |
| `region` | The region. `Belgium` if left out. |
| `cache` | How to [cache](/docs/sources/#caching) the forecast. Optional. |
The regions are `Belgium`, `Brussel`, `Antwerpen`, `OostVlaanderen`, `VlaamsBrabant`, `Limburg`, `WestVlaanderen`, `Hainaut`, `Liege`, `Luxembourg`, `Namur` and `BrabantWallon`.
## What you get
[Section titled “What you get”](#what-you-get)
* The forecast in %, per hour, `PT1H`, by default, or per quarter-hour, `PT15M`.
* Forecasts stay valid for 15 minutes, so `cache: {}` works.
## Example
[Section titled “Example”](#example)
```yaml
type: elia_solar_forecast
region: WestVlaanderen
cache: {}
```
# Energy-cost tariff
> Serve the prices of your own tariff, written in energy-cost's format.
The `energy_cost` source serves the prices of a tariff you write yourself, in [energy-cost’s tariff format](/docs/energy-cost/tariffs/): a fixed price, a time-of-use price, or a price that follows the day-ahead market.
Install: `pip install "cofy-api[tariff,energy-cost]"`
Fits: [`tariff`](/docs/modules/tariff/), [`directive`](/docs/modules/directive/) and [`timeseries`](/docs/modules/timeseries/).
## Settings
[Section titled “Settings”](#settings)
| Field | What it is |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tariff` | The [tariff](/docs/energy-cost/tariffs/): a list of versions, or a [resource](#sharing-a-tariff) holding one. Required. |
| `cost_group` | Which prices to serve: `consumption`, `injection`, `capacity` or `fixed`. If left out, each request picks one with a `cost_group` parameter, `consumption` by default. |
| `cache` | How to [cache](/docs/sources/#caching) prices. Optional; give it a `max_age`. |
## What you get
[Section titled “What you get”](#what-you-get)
* Prices in EUR/MWh: the sum of the cost types in the chosen group.
* Resolutions `PT5M`, `PT15M`, `PT1H`, `P1D` and `P7D`.
* A tariff that [follows an index](/docs/energy-cost/tariffs/#price-per-mwh) can only be asked for at the resolution of that index, such as `PT15M`.
Only [prices per MWh](/docs/energy-cost/tariffs/#price-per-mwh) and [time of use](/docs/energy-cost/tariffs/#time-of-use) can be served as a price per moment. Fixed amounts, tiers, lowest-or-highest, by-meter-type and capacity prices can’t.
## Example
[Section titled “Example”](#example)
A dynamic tariff following the Belgian day-ahead price, at `/tariff/dynamic/v1`, and its injection price at `/tariff/dynamic/v1?cost_group=injection`:
```yaml
type: tariff
name: dynamic
description: Our dynamic tariff, following the Belpex.
source:
type: energy_cost
tariff:
- start: 2024-01-01T00:00:00+01:00
consumption:
constant_cost: 10.0
variable_costs:
- { index: Belpex15min, scalar: 1.05 }
injection:
constant_cost: 14.56
variable_costs:
- { index: Belpex15min, scalar: -0.9876 }
```
The index has to be known to the Cofy API; ask whoever runs it which indexes are available.
## Sharing a tariff
[Section titled “Sharing a tariff”](#sharing-a-tariff)
A tariff used in several places can be a [resource](/docs/concepts/#resource) of type `tariff`:
```yaml
resources:
- type: tariff
name: dynamic
value:
- start: 2024-01-01T00:00:00+01:00
consumption: { constant_cost: 95.0 }
modules:
- type: tariff
name: dynamic
source:
type: energy_cost
tariff: { type: resource, name: dynamic }
cost_group: consumption
```
# EnergyID production
> The production of a meter, from an EnergyID record.
The `energyid_production` source provides the energy produced by a meter, from an [EnergyID](https://www.energyid.eu/) record.
Install: `pip install "cofy-api[production,energyid]"`
Fits: [`production`](/docs/modules/production/), [`directive`](/docs/modules/directive/) and [`timeseries`](/docs/modules/timeseries/).
## Settings
[Section titled “Settings”](#settings)
| Field | What it is |
| ----------- | --------------------------------------------------------------------------------------------------------------- |
| `api_key` | A [secret](/docs/sources/#credentials-secrets) holding an EnergyID API key with access to the record. Required. |
| `record_id` | The EnergyID record. Required. |
| `cache` | How to [cache](/docs/sources/#caching) the data. Optional; give it a `max_age`. |
## What you get
[Section titled “What you get”](#what-you-get)
* Production in the unit EnergyID keeps it in, normally kWh.
* Resolutions `PT5M`, `PT15M`, `PT1H`, `P1D`, `P7D`, `P1M` and `P1Y`. Without a `resolution`, you get 5 minutes.
* Data is requested from EnergyID per day, so ask for whole days.
## Example
[Section titled “Example”](#example)
```yaml
type: energyid_production
api_key: { type: secret, name: energyid_key }
record_id: your-record-id
cache: { max_age: PT15M }
```
# ENTSO-E day-ahead prices
> Day-ahead electricity prices of a European bidding zone.
The `entsoe_day_ahead` source provides the day-ahead electricity price of a bidding zone, in EUR/MWh, from the [ENTSO-E Transparency Platform](https://transparency.entsoe.eu/).
Install: `pip install "cofy-api[tariff,entsoe]"`
Fits: [`tariff`](/docs/modules/tariff/), [`directive`](/docs/modules/directive/) and [`timeseries`](/docs/modules/timeseries/).
## Settings
[Section titled “Settings”](#settings)
| Field | What it is |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `api_key` | A [secret](/docs/sources/#credentials-secrets) holding your ENTSO-E API key. Required. |
| `country_code` | The bidding zone, such as `BE` or `NL`. If left out, each request picks one with a `country_code` parameter, `BE` by default. |
| `cache` | How to [cache](/docs/sources/#caching) prices. Optional. |
You get an API key by registering on the Transparency Platform and asking ENTSO-E for API access.
## What you get
[Section titled “What you get”](#what-you-get)
* Prices in steps of 15 minutes, `PT15M`, the only resolution. Older hourly prices are repeated for each quarter-hour.
* Prices stay valid for an hour, so `cache: {}` works.
* A period without prices yet, such as tomorrow before the auction, gives an empty series.
## Example
[Section titled “Example”](#example)
```yaml
type: entsoe_day_ahead
country_code: BE
api_key: { type: secret, name: entsoe_key }
cache: {}
```
# EvO connection forecast
> The forecast net volume of a connection, from Energie van Ons.
The `acc_forecast` source provides the forecast net volume of one connection, from [Energie van Ons](https://energie.vanons.org/) (EvO): kWh per quarter-hour, positive for what the connection takes from the grid and negative for what it puts on it.
Install: `pip install "cofy-api[acc]"`
Fits: the [`simultaneity` source](/docs/modules/simultaneity/#the-simultaneity-source), [`directive`](/docs/modules/directive/) and [`timeseries`](/docs/modules/timeseries/).
## Settings
[Section titled “Settings”](#settings)
| Field | What it is |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ean` | The connection’s 18-digit EAN. Required. |
| `credentials` | A [secret](/docs/sources/#credentials-secrets) holding the service account key, in JSON, that EvO provided. Put the JSON on a single line. Required. |
| `cache` | How to [cache](/docs/sources/#caching) the forecast. Optional; give it a `max_age`. |
## What you get
[Section titled “What you get”](#what-you-get)
* kWh per quarter-hour, `PT15M`, the only resolution.
## Example
[Section titled “Example”](#example)
Simultaneity across two connections:
```yaml
type: simultaneity
sources:
- type: acc_forecast
ean: "541234567890123456"
credentials: { type: secret, name: evo_credentials }
cache: { max_age: PT15M }
- type: acc_forecast
ean: "541234567890123457"
credentials: { type: secret, name: evo_credentials }
cache: { max_age: PT15M }
```
# NED production forecast
> The forecast production of Dutch solar and wind installations.
The `ned_production_forecast` source forecasts what a set of Dutch solar and wind installations will produce, in kWh, from the [Nationaal Energie Dashboard](https://ned.nl/). For each installation it takes NED’s forecast for its region and type, scaled to its peak power.
Install: `pip install "cofy-api[ned]"`
Fits: [`timeseries`](/docs/modules/timeseries/) and [`directive`](/docs/modules/directive/).
## Settings
[Section titled “Settings”](#settings)
| Field | What it is |
| --------------- | ------------------------------------------------------------------------------------------------------- |
| `api_key` | A [secret](/docs/sources/#credentials-secrets) holding a Nationaal Energie Dashboard API key. Required. |
| `installations` | One or more installations, below. Required. |
| `cache` | How to [cache](/docs/sources/#caching) the forecast. Optional. |
Each installation has:
| Field | What it is |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `region` | `Nederland`, a province (`Groningen`, `Friesland`, `Drenthe`, `Overijssel`, `Flevoland`, `Gelderland`, `Utrecht`, `Noord-Holland`, `Zuid-Holland`, `Zeeland`, `Noord-Brabant`, `Limburg`) or `Offshore` |
| `type` | `solar`, `wind` or `wind_offshore`; `wind_offshore` only for `Nederland` or `Offshore` |
| `peak_power` | The installation’s peak power, in kW |
## What you get
[Section titled “What you get”](#what-you-get)
* The total forecast production of all installations, in kWh per step: per hour, `PT1H`, by default, or per quarter-hour, `PT15M`.
* Forecasts stay valid for 15 minutes, so `cache: {}` works.
## Example
[Section titled “Example”](#example)
A 100 kW solar park in Utrecht and a 2 MW wind turbine in Zeeland:
```yaml
type: ned_production_forecast
api_key: { type: secret, name: ned_key }
installations:
- { region: Utrecht, type: solar, peak_power: 100 }
- { region: Zeeland, type: wind, peak_power: 2000 }
cache: {}
```