# ERE (Emissiereductie-eenheden)

> Read charging-session and charging-station data for Emission Reduction Units, over per-user OAuth or a provider API credential.

> **Preview / beta**
>
> The Application Marketplace is under active development and may change without long deprecation windows. See the [Application Marketplace overview](/docs/platform/integrations/marketplace).

ERE (Emission Reduction Units, in Dutch *Emissiereductie-eenheden*) are tradeable certificates introduced under the European RED III directive that represent greenhouse-gas emission reductions in transport. Verified EV-charging data is the evidence used to generate them: every kWh delivered to a vehicle can contribute, and home-charging volumes can participate when bundled through an authorised service provider. For background, see [How do Emission Reduction Units work?](https://www.e-flux.io/ere-how-do-emission-reduction-units-work).

The ERE integration exposes the charging-session and charging-station data an application needs to evidence that supply and generate the units. There are two ways to consume it:

- **Per-user OAuth (marketplace application).** The customer authorises your application with the `ere` scope and shares specific charging stations; your application reads their data on their behalf. It is the first integration built on the marketplace's scoped data APIs.
- **Provider API credential.** A provider shares charging stations with an ERE partner centrally: a provider admin grants charging stations to a specific API credential in the dashboard, and that credential pulls a single server-to-server feed. Built for providers whose customers are managed through the API and never log in, so there is nobody to complete a per-user consent.

Both modes serve the same resources with the same semantics; where behaviour differs (authorisation, scoping, pagination), this page calls it out. The [API reference](#reference) documents the request and response schemas; this page explains the semantics behind them.

## The flow

With per-user OAuth:

1. The customer authorises your application with the `ere` scope (see [Authenticating your application](/docs/platform/integrations/marketplace/authentication)).
2. As part of consent, the customer chooses which locations and EVSEs to share (see [Consent, grants and data sharing](/docs/platform/integrations/marketplace/consent-and-sharing)).
3. Your application calls the ERE endpoints with the access token and receives only the shared, owned data for that user.

With a provider API credential:

1. The provider admin creates an API credential with the **ERE Data API** permission (dashboard, **Developer Menu → API Credentials**).
2. The provider admin grants charging stations to that credential in the dashboard, under [Charging Stations](https://{{customDNS}}/charging-stations) → **Integrations → ERE data sharing**: individual charging stations, all charging stations of a customer account, or every charging station of the organisation.
3. The integrator calls the ERE endpoints with the credential's token and the `Provider` header, and receives only the data of the granted charging stations.

## Prerequisites

With per-user OAuth:

- The application has been granted the `ere` scope. The ERE endpoints require it.
- `ere` is a **gated scope**: it cannot be requested through dynamic self-registration. Your application must be registered as a curated template or a provider installation. See [Registering an application](/docs/platform/integrations/marketplace/registration) and contact us at <ere.marketplace@road.io> to get set up.
- The customer has an active data-sharing selection. With nothing shared, the endpoints return an empty `200`, not an error, flagged by the `X-Data-Sharing-Status` response header (see [Empty results](#empty-results)).

With a provider API credential:

- **ERE data sharing is enabled for the provider.** It is a per-provider module, off by default; the provider asks their Road contact to enable it. Credential calls return a `403` until it is.
- The credential carries the **ERE Data API** permission; without it the endpoints return a `403`.
- The credential has at least one active grant. With nothing granted, the endpoints return an empty `200`, not an error, flagged by the `X-Data-Sharing-Status` response header (see [Empty results](#empty-results)).

## Authorising for ERE

### With an OAuth token

Request the `ere` scope on the authorisation request, for example:

```
scope=openid+offline_access+ere
```

Because `ere` is a resource-selectable scope, the consent flow includes the data-sharing step where the customer picks the locations and EVSEs to expose.

### With an API credential

Send the credential's token as a Bearer token, plus your provider ID in the `Provider` header, on every request:

```
Authorization: Bearer {api_token}
Provider: {{providerId}}
```

There is no consent step in the API: which charging stations the credential may read is set by the provider admin's grants (see the flow above), not by the caller. A grant covers a single charging station, every charging station of a customer account, or every charging station of the organisation; account-wide and organisation-wide grants automatically include charging stations added later.

## The data model

The integration serves two resources that key onto each other:

- **Chargers** (`GET /1/ere/chargers`): the shared charging stations, with their technical capabilities, location and reimbursement context. With an OAuth token the full shared set is returned in one response, with no pagination. With an API credential the response is paginated (`limit`/`skip`, total in `meta.total`), since a grant can cover thousands of charging stations.
- **Sessions** (`GET /1/ere/sessions`): the completed charging sessions (CDRs) delivered on those charging stations, served as an incremental change feed.

A session's `chargePointId` matches the charging station's `chargePointId` (the EVSE id), so sessions can always be attributed to a charging station from the chargers feed. Each sessions response also carries `meta.nextSince`, the delta-sync cursor, covered below.

Every field on both resources, with its type and meaning, is documented in the API reference: [ERE Sessions](/docs/platform/reference/platform-api/ere-beta#getv1eresessions) and [ERE Chargers](/docs/platform/reference/platform-api/ere-beta#getv1erechargers). Four carry meaning their names do not:

- **`sessionId`** is your upsert key when ingesting the feed (see [Ingesting sessions](#ingesting-sessions-delta-sync)).
- **`meterStart`** and **`meterStop`** are raw OCPP register readings in **Wh**, not kWh like `energyKwh`. A `0` is a real reading, and both are always present.
- **`tokenIdHash`** is a SHA-256 hash of the charge token: a stable pseudonymous id that groups sessions on the same token without exposing it.
- **`location.coordinates`** on a charging station is the WGS84 position of its location as `latitude` and `longitude` in decimal degrees. It is omitted when the location has no position recorded, so treat it as optional and fall back to the postal address. Sessions carry the address only.

## Which sessions are returned

A session appears in the feed when **all** of the following hold:

1. **It ran on a charging station within scope.** For OAuth, a charging station the user owns and has shared; for an API credential, a charging station covered by an active grant. See [How returned data is scoped](#how-returned-data-is-scoped).
2. **It belongs to the organisation in scope**: the provider and account of the user who authorised your application (OAuth), or the credential's provider (API credential).
3. **It has ended.** In-progress sessions never appear; a session enters the feed once it completes.
4. **It passed data-integrity validation.** Sessions invalidated by the platform (for example missing or unknown meter values, duplicated transactions, or implausible energy readings) are excluded. This is why `meterStart`/`meterStop` are always present: a completed session without meter values cannot reach the feed.

**Billing state is deliberately not a filter.** Whether a session is excluded from reimbursement, whether it has been invoiced, and whether the tariff was zero-cost have no effect on the feed. A free-of-charge session still delivered real energy and still appears. The feed represents delivered energy, the evidence for emission-reduction claims, not billing.

## Ingesting sessions (delta sync)

```http
GET https://api.road.io/1/ere/sessions
Authorization: Bearer {access_token}
```

API-credential calls also send the `Provider` header; everything below applies to both modes.

| Query parameter | Purpose                                                                                                                                                                                           |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `updatedSince`  | Opaque delta-sync cursor. Pass back the `meta.nextSince` from a previous response verbatim; omit it on the first call. Do not parse or construct it; a malformed cursor is rejected with a `400`. |
| `limit`         | Maximum sessions to return per page. Default 500, maximum 1000.                                                                                                                                   |

### How the feed is ordered

The feed is ordered by **when a session's data last changed**, not by when the session took place. This is what makes delta sync work: if a session you already received is later corrected (a meter adjustment, a revalidation, a backfill), it is **re-emitted** and your next pull picks up the new version. A feed ordered by session start time could never surface a change to a session you already fetched.

The cursor (`meta.nextSince`) is an opaque token encoding a position in that order. Replaying it returns everything that changed after that position.

### The ingestion loop

1. **Initial sync**: call without `updatedSince` and keep following `meta.nextSince` until a page returns fewer than `limit` rows. You now have the full current state.
2. **Incremental sync**: poll with your stored cursor. Each page returns sessions changed since the cursor, oldest change first, plus a new `meta.nextSince`.
3. **Empty page**: nothing changed yet. The response echoes your cursor back; keep polling with it.

Sessions become visible shortly after they complete, so the polling interval is a product choice; daily is typical for booking workflows.

### Protecting your ingestion

- **Upsert by `sessionId`, never append.** The same session reappears whenever its data is corrected. Treat the feed as an upsert stream: insert if unseen, replace if seen.
- **Persist the cursor only after the page is durably processed.** Delivery is effectively at-least-once: if you crash after processing but before saving the cursor, you will see the same page again, which is harmless if your writes are idempotent upserts.
- **Never assume completeness for a time window.** Because ordering follows update time, a historical session can enter the feed at any point, for example after a platform-side correction touches it. Do not conclude "I have all sessions up to date X" from `startTime`; completeness only exists relative to your cursor position.
- **The feed does not retract.** A session that is later invalidated or deleted on the platform is not re-emitted or tombstoned; it simply stops being part of a from-scratch sync. If your process requires strict reconciliation, periodically run a full re-sync (omit `updatedSince`) and diff against your store.
- **Store the cursor as an opaque string.** Its internal format may change; parsing or constructing it will break.

## Fetching charging stations

```http
GET https://api.road.io/1/ere/chargers
Authorization: Bearer {access_token}
```

The chargers feed returns metadata for the shared charging stations (paginated for an API credential, as noted above). Refresh it periodically, or before attributing sessions, rather than caching it indefinitely: the shared set changes whenever a sharing selection or grant is edited, or ownership changes.

## How returned data is scoped

### With an OAuth token

The data an ERE call returns is the intersection of two things:

- **Ownership.** Only charging stations the user actually owns are eligible (their own, typically home, charging stations). This ownership rule is specific to ERE: an account administrator's broader account access does not extend here, so another user's employee-reimbursement charging stations are never returned through ERE.
- **The data-sharing selection.** Within what the user owns, only the locations and EVSEs they have shared are returned. Sharing everything returns all eligible charging stations.

This intersection is recomputed on every call, so a charging station the user no longer owns, or has stopped sharing, stops appearing immediately, along with its sessions.

### With an API credential

The set is the credential's active grants, resolved on every call: explicit charging station grants are re-checked against the live charging station, and account-wide and organisation-wide grants expand to the charging stations currently under them, so charging stations added later flow automatically. A revoked grant, or a charging station that leaves the granted account or the organisation, stops appearing immediately, along with its sessions.

Data never crosses the organisation's boundary: only charging stations of the credential's provider (and its sub-providers) can be granted, and only the provider's own admins can manage grants.

## Empty results

An ERE call with nothing to return is a successful, empty `200`, never an error: the OAuth grant or the credential is still valid, there is simply no shared data. Every response carries an `X-Data-Sharing-Status` header so you can tell an empty feed apart from a misconfiguration without guessing:

| Value    | Meaning                                                                                                                                                                                                                                                         |
| -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `active` | A sharing selection or grant exists. The feed reflects it, even when it currently resolves to zero charging stations (for example everything is un-shared for now, or the shared charging stations are no longer owned). An empty feed here is genuinely empty. |
| `none`   | Nothing is shared to resolve at all: the customer has shared nothing (OAuth), or the credential has no active grants (API credential). This is the case to surface.                                                                                             |

The header is always present, on both `/1/ere/sessions` and `/1/ere/chargers` and in both modes.

On `none` with an OAuth token, prompt the customer to review their sharing at **Settings → Personal → Connected apps**. On `none` with an API credential, the provider admin manages grants at [Charging Stations](https://{{customDNS}}/charging-stations) → **Integrations → ERE data sharing**. A `403` on a credential call is a different signal: the credential lacks the ERE Data API permission, or ERE data sharing is not enabled for the provider.

A `401` is a different signal entirely: the token or the grant itself is no longer valid, and if a refresh also fails the customer has disconnected your application. See [Using the API](/docs/platform/integrations/marketplace/using-the-api) for telling these cases apart.

## Reference

- [ERE Sessions API reference](/docs/platform/reference/platform-api/ere-beta#getv1eresessions)
- [ERE Chargers API reference](/docs/platform/reference/platform-api/ere-beta#getv1erechargers)
