# Road Technology Documentation
> Everything an agent needs to understand and support the Road EV-charging platform: a plain-English e-mobility primer, the platform data model and guides (including single sign-on and the application marketplace), and the Support Agent MCP tools. The full API reference is published separately under /docs. Point an agent at /llms-full.txt for the complete documentation in one file.
---
> Source: https://technology.road.io/docs/emobility/what-emobility-is
# What e-mobility is
E-mobility is the mix of hardware, software and commercial agreements that lets an electric vehicle charge and pay for energy, anywhere, across networks that are owned and run by different companies. A driver should be able to pull up to more or less any public charging station, start a session, and be billed correctly, without caring who owns the charging station or which app they signed up with.
## Why it is harder than a card payment
A card payment is one shopper, one terminal, one bank rail. A charging transaction is not.
- **Two sides that rarely belong to the same company.** The charging station is operated by one business, and the driver's account or card is issued by another. They have to agree, in real time, that this driver may charge here and who will pay.
- **Closed-loop authorisation.** Most public charging is authorised with an RFID card or an app token over a private network, not with a bank card at the point of use. The money is reconciled afterwards between the operators.
- **Real-time state.** Whether a charging station is free, how much power it can deliver, and what it costs are all published live across the network so drivers can find and trust a charge.
- **Metered, variable cost.** The price depends on energy delivered, time, location and tariff, and is only final once the session ends.
- **Settlement between businesses.** After the session, the operators settle with each other, and the driver's provider bills the driver. One physical charge can touch four or five systems in under a second.
## The shape of the ecosystem
Two roles sit at the centre. A **Charge Point Operator (CPO)** owns and runs the charging hardware. An **eMobility Service Provider (eMSP)** gives drivers a way to access charging (a card, an app, an account) and bills them. **Roaming** is the set of agreements and interfaces that let an eMSP's drivers use a CPO's charging stations even when the two are different companies. **Hubs** interconnect many CPOs and eMSPs so each one integrates once instead of many times.
Two open protocols carry almost all of this. **OCPP** connects a charging station to the software that manages it. **OCPI** connects operators to each other for roaming. The rest of this primer walks through the players, the anatomy of a single charging session, and those two protocols, and ends with a glossary of the vocabulary you will meet everywhere else in these docs.
---
> Source: https://technology.road.io/docs/emobility/the-players
# Who's who
A handful of roles show up in every conversation about charging. Learn these and most of the sector makes sense.
## The hardware
- **Charging station.** The physical unit installed at a site. A station contains one or more EVSEs.
- **EVSE** (Electric Vehicle Supply Equipment). A single charging point within a station, the part that delivers one charge at a time.
- **Connector.** The socket or tethered cable on an EVSE (Type 2, CCS, CHAdeMO, and so on). An EVSE can expose more than one connector, though usually only one charges at once.
This hardware hierarchy, station to EVSE to connector, is exactly how the protocols model it, so it is worth holding on to. One warning: "charge point" is used loosely across the industry, sometimes for the whole station and sometimes for a single EVSE, so read it in context.
## The businesses
- **CPO, Charge Point Operator.** Owns or operates the charging stations and keeps them running. To do that a CPO uses a **CPMS** (Charge Point Management System, which OCPP 2.0.1 calls the CSMS): the software that talks to every station, tracks status, manages tariffs and maintenance, and records sessions. The CPMS is usually provided by a platform, with the CPO as its customer. This is one of the things Road provides.
- **eMSP, eMobility Service Provider** (sometimes just MSP). Faces the driver. Issues charge cards or virtual tokens, provides the app and account, sets the driver's price, and bills them. An eMSP owns customers, not charging stations.
- **Driver.** The person charging a vehicle, holding a card or app from an eMSP.
A single company is often both a CPO and an eMSP, but the roles stay distinct because roaming depends on telling them apart.
## Roaming and hubs
No operator has charging stations everywhere, and no provider's card works everywhere by default. **Roaming** closes that gap: a CPO agrees to let another eMSP's drivers charge, and to be paid for it. Done one-to-one this is a mesh of point-to-point integrations. A **roaming hub** (for example Gireve or Hubject, or Road's own Roaming Hub) sits in the middle so each party integrates with the hub once and reaches everyone connected to it.
## Where a platform like Road sits
A platform such as Road provides the software for all three: the CPMS a CPO needs to run stations, the tools an eMSP needs to issue cards and bill drivers, and a **roaming hub** that connects CPOs and eMSPs and extends coverage. It also offers roaming as a service, so a smaller operator can reach a large network without building its own agreements. The result is interoperability: integrate once, reach every connected network.
---
> Source: https://technology.road.io/docs/emobility/anatomy-of-a-charging-session
# Anatomy of a charging session
A charging session runs through several steps, from identifying the driver to settling the payment. This page walks through each one and the protocol that carries it.
## 1. The driver identifies themselves
The driver presents a **token**: an RFID card tapped on the reader, a button in an app, an auto-charge trigger, or **Plug & Charge** where the car itself identifies over ISO 15118. Either way the charging station now has a token and a request to start.
## 2. Authorisation
The charging station asks its management system whether this token may charge, over **OCPP** (an `Authorize` request, or the identifier carried on the transaction start). Two cases:
- **On-network.** The token belongs to the operator's own drivers, so the CPMS decides directly.
- **Roaming.** The token belongs to another provider, so the CPO asks that eMSP in real time and gets a yes or no in a few hundred milliseconds. This usually runs over **OCPI** (a token authorization), though other roaming protocols exist too.
Only after a yes does energy flow.
## 3. The session runs
A **transaction** opens on the charging station and the meter starts. The charging station streams **meter values** (energy, sometimes power and other readings) to the management system throughout, over OCPP. The applicable **tariff** is tracked against those readings so the running cost is always current, not estimated afterwards. During a roaming session the CPO can also publish a live **Session** object to the eMSP over OCPI so the driver's app can show progress.
## 4. The session ends
The driver stops the charge or unplugs, the transaction closes, and the charging station reports the final energy delivered and duration.
## 5. The billable record (CDR)
The session is turned into a **Charge Detail Record (CDR)**: the authoritative, final record of what was delivered and what it cost. For a roaming session the CPO sends the CDR to the eMSP over OCPI. A CDR is immutable once sent; a correction is issued as a separate credit CDR rather than an edit. The CDR, not the live session, is what money is based on.
## 6. Settlement
Finally the money moves. Across roaming, the CPO and eMSP settle with each other on the exchanged CDRs. The eMSP bills the driver according to the price it set, which need not match what it paid the CPO. A platform in the middle reconciles all of this: what the driver paid, what the operator earns, and what clears between parties.
## When a session does not count
Not every transaction becomes a clean, billable CDR. Sessions can be **excluded** or flagged for reasons such as a failed or unauthorised start, a simulated or test session, a duplicate, fraud checks, or data-integrity problems. Reconciliation has to account for these so the books balance, which is why a real platform tracks exclusion reasons explicitly rather than assuming every plug-in is a sale.
---
> Source: https://technology.road.io/docs/emobility/ocpp
# OCPP: talking to charging stations
**OCPP** (Open Charge Point Protocol), from the Open Charge Alliance, is the open standard between a charging station and the back office that manages it. It is what lets an operator run charging stations from many manufacturers with one system. In modern use it runs as JSON messages over a WebSocket that the station opens to the back office, so commands flow both ways over one long-lived connection.
## The relationship
One side is the **charging station**, the other is the **central management system**. The names changed between versions, but the shape did not: the station connects out to the back office, reports what it is doing, and accepts instructions.
## Versions
- **1.6J.** The workhorse, deployed almost everywhere. Roles are **Charge Point** and **Central System**. Its device model is a charging station with one or more **connectors**, numbered from 1, where connector `0` means the whole unit. There is no separate EVSE concept. Around 28 messages, grouped into feature profiles (Core, Smart Charging, Firmware, Local Auth List, Reservation, Remote Trigger). The `J` means the JSON-over-WebSocket variant.
- **2.0.1.** A larger redesign. Roles are **Charging Station** and **CSMS** (Charging Station Management System). It introduces a three-level device model, station to **EVSE** to **connector**, where `evseId=0` addresses the whole station. Transactions are reported through a single richer `TransactionEvent` message. It adds a real security profile, a queryable device model, and support for ISO 15118 Plug & Charge. Around 64 messages, grouped into functional blocks.
- **2.1.** Same roles and device model as 2.0.1, with about 27 new messages on top for where the grid is heading: **DER control** (managing distributed energy resources), **V2X / bidirectional** charging (energy flowing back out of the car), richer **tariff and cost**, **battery swap**, periodic event streams, and dynamic charging.
If you see `StartTransaction` and `idTag`, that is 1.6. If you see `TransactionEvent` and `evseId`, that is 2.x.
## The message flows that matter
- **Boot and heartbeat.** On connect the station sends `BootNotification` (who am I, what firmware), then periodic heartbeats so the back office knows it is alive.
- **Status.** The station reports connector or EVSE state (available, occupied, faulted) as it changes.
- **Authorisation.** Before or as a charge starts, the station checks a presented token with the back office (`Authorize`).
- **Transaction and metering.** A transaction opens, meter values stream during the charge, and the transaction closes with final readings. In 1.6 this is `StartTransaction` / `MeterValues` / `StopTransaction`; in 2.x it is `TransactionEvent` plus meter values.
- **Remote control.** The back office can start, stop or unlock a charge remotely (`RemoteStartTransaction` in 1.6, `RequestStartTransaction` in 2.x).
- **Smart charging.** The back office sends charging profiles that cap or shape power over time (`SetChargingProfile`), to respect grid limits or a site's capacity.
- **Firmware and diagnostics.** The back office can push firmware updates and pull logs and diagnostics.
- **Security.** 2.x adds authenticated, certificate-based connections and secure firmware, rather than relying on the network alone.
## At Road
Road runs the back-office side of OCPP at scale: an OCPP gateway and CSMS that terminate these connections, authorise charges, record transactions and apply smart-charging limits, across a large fleet of stations and manufacturers. The protocol details above are the standard; how Road implements them is covered in the platform and stack docs.
---
> Source: https://technology.road.io/docs/emobility/ocpi
# OCPI: roaming between networks
**OCPI** (Open Charge Point Interface), from the EVRoaming Foundation, is a widely used open standard between operators. It is not the only roaming protocol (others such as OCHP and eMIP exist), but it is the one these docs focus on. Where OCPP connects a charging station to its own back office, OCPI connects **CPOs and eMSPs to each other** so a driver on one network can charge on another and everyone gets paid. It is an HTTP REST API exchanging JSON, used peer to peer between two parties or through a roaming hub.
## Getting connected: credentials
Two parties start with the **Credentials** handshake: they exchange tokens and the URL of each other's version and module endpoints, agreeing which OCPI version and which modules they will use. After that, each side calls the other's endpoints directly.
## The core modules
OCPI is a set of modules, each a small API. A party implements the ones its role needs.
- **Locations.** Where the charging stations are and what they offer: sites, EVSEs, connectors, power, and availability. The CPO publishes; the eMSP consumes to show drivers where they can charge.
- **Tokens.** The identifiers that authorise charging (RFID cards, app tokens) and the real-time **authorization** of them. The eMSP owns its tokens; the CPO checks them.
- **Sessions.** Live and completed charging sessions, so the eMSP can follow a session its driver is running on the CPO's network.
- **CDRs.** Charge Detail Records, the final billable record of each session, sent from CPO to eMSP. These drive settlement.
- **Tariffs.** Pricing information, so a session's cost can be understood and shown.
- **Commands.** Remote actions across the roaming link: start, stop, reserve, unlock.
- **Credentials.** The registration and handshake described above.
- Later versions add more: **ChargingProfiles** and **HubClientInfo** in 2.2.1, and a **Payments** module in 2.3.0.
## Token authorization
When a driver presents a token on a charging station that belongs to another operator, the CPO authorises it in real time against the token's eMSP over the Tokens module (or a cached, authorised copy). That check returns a yes or no in a few hundred milliseconds, so a driver on another operator's charging station is authorised almost instantly.
## CDR reconciliation
Settlement runs on CDRs, so the rules around them are strict. A CDR is **immutable once sent**: if something was wrong, the fix is a separate credit CDR, never an edit. Parties keep in sync using delta queries driven by a `last_updated` timestamp, pulling only what changed. Getting CDR exchange right, matching them to sessions, handling corrections, and reconciling what each party owes, is most of the real work of roaming.
## Versions
- **2.1.1.** Older but still active across the network. Markdown spec.
- **2.2.1.** The current mainstream version. Adds `country_code` and `party_id` on objects, explicit sender and receiver roles per endpoint, the ChargingProfiles and HubClientInfo modules, and `session_id` on CDRs.
- **2.3.0.** The newest, which adds the Payments module.
A hub such as Gireve or Hubject speaks OCPI to everyone connected, so a party integrates once and reaches the whole network behind the hub.
## At Road
Road operates a roaming hub and speaks OCPI as both CPO and eMSP. An operator can connect over OCPI to reach Road's network, take roaming as a managed service, or use the hub purely as OCPI infrastructure while keeping its own agreements. The roaming section of these docs covers Road's implementation; the detail above is the standard itself.
---
> Source: https://technology.road.io/docs/emobility/glossary
# Glossary
Short definitions of the terms you will meet across these docs.
- **AFIR.** The EU's Alternative Fuels Infrastructure Regulation, which sets requirements for public charging coverage, ad-hoc payment and price transparency. Part of why interoperability and clear pricing matter.
- **CDR (Charge Detail Record).** The final, authoritative record of a completed session: energy, duration and cost. Billing and settlement run on CDRs. Immutable once sent; corrected with a credit CDR.
- **Charging station.** The physical unit installed at a site; contains one or more EVSEs. Loosely called a "charge point", a term also used for a single EVSE, so read it in context.
- **Connector.** The socket or tethered cable on an EVSE (for example Type 2, CCS, CHAdeMO).
- **CPMS (Charge Point Management System).** The software a CPO uses to run its charging stations: status, tariffs, sessions, maintenance. The industry term; OCPP 2.0.1 calls the same system the CSMS.
- **CPO (Charge Point Operator).** Owns and operates the charging hardware.
- **CSMS (Charging Station Management System).** The OCPP 2.0.1 name for the back office a charging station connects to, the CPMS in this primer. The 1.6 name is Central System.
- **eMSP / MSP (eMobility Service Provider).** Faces drivers: issues cards and apps, sets their price, and bills them. Owns customers, not charging stations.
- **EVSE (Electric Vehicle Supply Equipment).** A single charging point within a station, delivering one charge at a time.
- **Hub (roaming hub).** A connector between many CPOs and eMSPs so each integrates once instead of many times (for example Gireve, Hubject).
- **ISO 15118 / Plug & Charge.** A standard that lets the vehicle identify and authorise itself to the charging station, so charging starts on plug-in with no card or app.
- **kWh (kilowatt-hour).** The unit of energy delivered, and usually the basis of price.
- **Location.** In OCPI, a charging site and everything at it (EVSEs, connectors, availability).
- **OCPI (Open Charge Point Interface).** The open protocol between operators (CPO and eMSP), used for roaming.
- **OCPP (Open Charge Point Protocol).** The open protocol between a charging station and its management system.
- **RFID.** The contactless card technology commonly used to authorise a charge.
- **Roaming.** Agreements and interfaces that let one provider's drivers charge on another operator's network.
- **Session.** A single charging event, from start to stop.
- **Settlement.** Reconciling and moving money after sessions: between CPO and eMSP, and from eMSP to driver.
- **Smart charging.** Shaping or capping charging power over time to respect grid or site limits.
- **Tariff.** The pricing rules for a charge (per kWh, per minute, session fees, time-of-use).
- **Token.** The identifier that authorises charging: an RFID card, a virtual card, or an app credential.
- **Transaction.** The charging station's record of a charge in OCPP, which becomes a session and then a CDR.
- **V2G / V2X (Vehicle-to-Grid / Vehicle-to-Everything).** Bidirectional charging, where energy can flow out of the vehicle as well as in.
- **White label.** Running Road's software under another company's brand.
---
> Source: https://technology.road.io/docs/platform/introduction/overview
# How the platform works
Road runs EV charging. On the operator side it runs charging stations, the **CPO** role, a charging station management system. On the driver side it issues the cards and tokens people charge with, and bills for them, the **eMSP** role. Most customers run both, against the same accounts, and the two sides share one model that the rest of these docs build on.
## The data model
Everything on the platform belongs to a **Provider**, the tenant. Under a Provider sit **Accounts**, and the Account is the unit that owns the working parts: users, charging infrastructure, cards and sessions. Billing settles on top of the Account.
- **Provider** is the top of the tree, the tenant a customer is set up as, and every other object is owned somewhere beneath it. A Provider can nest **sub-providers** for a group that sits over several underlying entities, and it may be a Private Label running the platform under its own brand, a single operator running only its own charging, or an eMSP. CPOs usually join a Provider as Accounts. [Providers, accounts and users](/docs/platform/account-management/providers-accounts-and-users) covers the variations.
- **Account** is where ownership and money sit. It owns the locations and the cards, carries the socket fees for its sites and the cost of the sessions its users run, and is the entity Road bills and reimburses.
- **Users and administration.** Users are the logins within an Account. There is no separate directory of administrators: a Provider administrator is simply a User whose roles are assigned at the **Provider** level rather than only within one Account. Roles decide what a user may do and at what scope, but the Account still owns whatever they create or spend.
- **Location** is a physical site, and it carries the policy for the stations on it: whether and how the site is **reimbursed** for the energy it delivers, and whether it is **published to the roaming network** so other networks' drivers can find and use it.
- **EVSE Controllers and Connectors.** An EVSE Controller is a charging station at a Location, speaking OCPP and identified by an `evseId` such as `NL*EFL*EV*1234567`; its Connectors are the individual plugs. Two kinds of money attach to a connector: its **tariff** sets what a charge costs the driver (a **tariff profile** applies one tariff across many stations), and its **billing plan** is the operator's monthly subscription for running that connector on Road.
- **Charge Cards and Tokens** are the eMSP side. A **Charge Card**, tag or app carries a **Token**, the credential a driver presents to charge. A token carries its own **billing plan**, which sets how the driver is charged for the energy they use.
- **Authorising a charge.** When a driver presents a token at a station, whether it may charge there is an authorisation check: the station and backend look the token up, usually over **roaming** against its home network, and allow or decline. A station can also carry an **Access Group** for local rules, such as letting a set of people charge for free there.
- **Records of a charge.** The operator side writes a **CPO session**, then sends it out over roaming as a **CDR** (Charge Detail Record, the standard billing record). If the token's eMSP is also on Road, that CDR comes back and becomes an **MSP session** for the driver's side. Road trades CDRs off the platform too: the eMSP side takes them in from other operators, and the CPO side sends them out to other eMSPs. The shape to remember:
**CPO session → CDR (over roaming) → MSP session**
A CPO session and an MSP session are two records of the same charge, linked by the CDR between them. Corrections are made with credit sessions, not edits.
## From a tap to an invoice
A single charge runs like this:
1. A driver presents a **Token** at a **Connector**.
2. The station and backend **authorise** it: they check whether the token is allowed to charge there, usually over roaming against its home network, and allow or decline.
3. On approval the charge runs and is metered through to the end.
4. The operator side writes a **CPO session** and sends a **CDR** over roaming. If the token's eMSP is on Road, that CDR returns as an **MSP session**.
5. **Billing** settles it from those records. On the CPO side the issued CDR feeds a **credit invoice** that reimburses the station owner for the energy delivered, at the EVSE's **tariff**. On the eMSP side the received CDR is billed to the card's owner for usage, alongside their card subscription.
The same shape holds whether the charge happened on the customer's own network or, through roaming, on someone else's. Only who reimburses whom changes.
## Two sides of one model
Every charge touches both roles: the CPO operates the station it happens on, and the eMSP serves the driver who runs it. Each keeps its own record of that charge.
| | CPO side | eMSP side |
| ---------- | --------------------------------------------------- | ------------------------------------------------------- |
| Operates | Locations, EVSE Controllers and Connectors | Charge Cards and Tokens |
| Per charge | Writes a CPO session, issues a CDR | Receives a CDR, turns it into an MSP session |
| Money | Reimbursed by a credit invoice for energy delivered | Bills the card holder for usage, plus card subscription |
The CDR is the link between them. Plenty of customers run both sides against the same Accounts, and the same `cpo`/`msp` split shows up across the model, on locations, billing plans and invoices as well as sessions.
## Beyond the core
A lot of functionality hangs off the core model, and the later sections take each in turn: dynamic and scheduled **pricing**, **billing plan** management, **EVSE hardware configuration** and operational control (remote start and stop, reboots, availability), **smart charging** and load management, **access control**, **payment terminals** at the station, **roaming** connections, and campaigns and promotions.
## Where to go next
- [Account management](/docs/platform/account-management/providers-accounts-and-users) for the tenancy model, sign-in and provisioning.
- [Charging station operation](/docs/platform/charge-point-operation/charge-stations) for stations, connectivity and sessions.
- [E-mobility services](/docs/platform/e-mobility-services/cards-tokens-and-access) for cards, tokens and access.
- The [API reference](/docs/platform/reference) for every endpoint behind the model.
> This section is the platform's own model. The roaming hub and ChargeNow are separate products with their own sections, and where the model touches roaming (tokens and CDRs) we note it and point onward rather than diving in here.
---
> Source: https://technology.road.io/docs/platform/account-management/providers-accounts-and-users
# Providers, accounts and users
Ownership on the platform rests on three entities: the **Provider**, the **Account** and the **User**. Everything else is owned somewhere beneath them.
## Provider
A **Provider** is the top-level tenant, a "super organisation". Every object on the platform is owned by a Provider. A Provider is usually one of:
- a **Private Label**: a business running the platform under its own brand and onboarding its own customers as **Accounts**,
- a **single operator**: a corporate or infrastructure owner using the platform only for its own charging, with Accounts mirroring its internal structure and no external customers,
- an **eMSP** that mainly issues charge cards.
The CPOs that run charging stations usually join a Provider as **Accounts**, not as Providers of their own.
Providers are created and configured by your Road account manager to fit the business. For complex organisations, **sub-providers** let a super organisation sit over several underlying entities.
## Account
An **Account** is the central organisational unit, owned by a Provider. It is the entity that actually owns the working parts of the platform: **Locations and charging stations, Charge Cards and their Tokens, and the Sessions run against them**. It is also the entity the platform bills, and the entity that receives reimbursements.
An Account is deliberately flexible. It can represent:
- a **customer** of a Private Label (where the Provider is a reseller), or
- an **internal structure** of a corporate, such as a department, branch or regional office.
Because the Account holds both the locations and the tokens, it carries the financial responsibility: the socket fees for its locations, and the cost of the charging sessions its users run. In most cases an Account is also reimbursed for sessions on the stations it owns, though some setups differ, home-charging reimbursement for example.
## User
A **User** is an individual login belonging to an Account. An Account can have many Users, so a team can manage charging together while billing and contractual responsibility stay with the Account.
A User's **roles** set what they can do, from full administrative control (billing, cards, locations) down to limited access (ordering a card, viewing their own sessions). Roles are assigned at two levels:
- **Account roles** apply within one Account, for the people who run its charging.
- **Provider roles** apply across the tenant. There is no separate directory of administrators: a Provider administrator is simply a User carrying provider-level roles.
Through their roles, a User can order and manage Charge Cards and Tokens, create and manage Locations and their charging stations, monitor charging sessions and their costs, and administer Account settings.
Users act, but the Account owns: whatever a User creates or spends is owned and settled at the Account level.
## Getting people onto an Account
People reach an Account in three ways:
- **They sign up.** Where you run a white-label dashboard or app, a customer can register through the web sign-up flow, which creates their Account and User for them. This is the usual path for consumer-facing setups.
- **You provision them.** Your own system creates the Accounts and Users and supplies their details. This suits a Private Label running its own signup, or anyone who runs their own sign-in and mirrors users onto Road. See the [Accounts](/docs/platform/reference/platform-api/accounts) and [Users](/docs/platform/reference/platform-api/users) endpoints.
- **You invite them.** Road emails the person, and they set their own password and finish their profile, becoming a User when they accept. Reach for this when you would rather someone onboard themselves than hold their credentials for them. See [Invites](/docs/platform/reference/platform-api/invites).
There is also a focused invite for bringing a customer onto a station you have already configured: Road emails them to set up their account for that charging station and start using it.
## Billing and reimbursement
An Account has two sides to its money, and they are deliberately separate:
- **Billing** is how the Account **pays**: its billing address and VAT details, whether collection is automatic or manual, and the bank details used to collect. This covers what the Account owes, its usage and any subscriptions.
- **Credit billing** is how the Account **gets paid**: the bank details Road uses to **reimburse** a CPO for the energy its stations deliver.
Many Accounts have both. A customer that both runs stations and issues cards pays for the charging its cards use through billing, and is reimbursed for the charging its stations deliver through credit billing.
Setting up the payment method a customer actually pays with, a card or a direct-debit mandate, happens through the dashboard's payment flow rather than by handing card details to the API. The billing fields themselves sit on the [Accounts](/docs/platform/reference/platform-api/accounts) endpoints.
## Account tiers
An **account tier** bundles a set of platform features and is priced by a billing plan. Putting an Account on a tier sets which features it has and what it pays to run on the platform. Tiers let a Provider offer levels, a basic tier and a richer one, say, and move Accounts between them as their needs change. The available tier plans are in the [Billing plans](/docs/platform/reference/platform-api/billing-plans) reference.
## Matching your records with externalId
Both Accounts and Users can carry an **`externalId`**, your own reference for them. Road keeps its own IDs; the `externalId` lets you line the two up without threading Road's IDs through your systems. It also does double duty for single sign-on: when SAML or OIDC is enabled, Road matches a sign-in to the User whose `externalId` equals the identifier your identity provider sends. See [Single Sign-On](/docs/platform/account-management/single-sign-on).
## Over their lifetime
Accounts and Users can be searched, updated and retired as things change. Removing an Account is a soft delete, so it can be restored if you need it back. The full set of operations, and the exact fields, live in the [API reference](/docs/platform/reference).
---
> Source: https://technology.road.io/docs/platform/account-management/single-sign-on
# Single Sign-On
Single Sign-On (SSO) lets your users reach Road with the credentials they already have, rather than a separate Road login. Road supports two standards, **SAMLv2** and **OpenID Connect (OIDC)**, and which one fits depends on who is signing in.
## Two ways to use it
### Corporate directory, over SAML
For a company that manages its people in a corporate directory, Road connects to that identity provider (IdP) over SAMLv2. The **SAML configuration is applied to a single Account**, so everyone who authenticates through the corporate IdP belongs to that Account, and the company keeps provisioning, roles and access policy in its own directory.
### Customer identities and social login, over OIDC
For a business whose users authenticate against an external IdP (WSO2, Auth0, Azure AD B2C and the like), Road connects over OIDC, with **each Account mapping its own IdP login to a Road user**. OIDC also drives **social login**, so users can sign in with Google, Apple, Facebook or another OIDC provider instead of a Road password.
### Provisioning on first login
Both SAML and OIDC can create a user automatically the first time they sign in, so you do not have to provision everyone up front. It is off by default and enabled per configuration.
## Choosing between them
- **SAML** fits corporate identity: one corporate directory, one Account, access managed centrally by the customer's administrators.
- **OIDC** is the more flexible fit for customer-facing sign-in and social login, configured per Account.
Use SAML to let a company manage its employees' access to Road through its directory; use OIDC for external customers or social login. See [SAML](/docs/platform/account-management/single-sign-on/saml) and [OpenID Connect (OIDC)](/docs/platform/account-management/single-sign-on/openid-connect) to set each up.
---
> Source: https://technology.road.io/docs/platform/account-management/single-sign-on/saml
# SAML
Road supports SAMLv2 single sign-on so a company can connect its own identity provider (IdP) and let its people into Road with their corporate credentials. Access is managed in the company's directory, not in Road.
## How SAML works
SAML brokers authentication between a **Service Provider (SP)**, here Road, and an **Identity Provider (IdP)**, the system that verifies the user. When a user tries to sign in, Road redirects them to the IdP; once the IdP has verified them it returns a signed **SAML assertion** confirming who they are, and Road grants access on the strength of it, with no separate Road password.
## SAML on Road
SAML configuration is applied at the **Account level**: every user who authenticates through the configured IdP becomes a member of that Account. That makes it the right fit for a corporate customer that wants to manage employee access to Road, onboarding and offboarding, through its own directory.
### Before you start
- The person configuring it has the **Account Administrator** role.
- SAML is **enabled at the Provider (tenant) level** by a Road administrator.
The SAML settings then live on the Organisation Profile Settings page, at [https://dashboard.road.io/settings/account/organization/sso/saml](https://{{customDNS}}/settings/account/organization/sso/saml).
### Configuring the connection
The setup is SAMLv2 compliant and has been tested against Okta, Google Workspace and Microsoft Entra ID (formerly Azure AD). Any SAMLv2 IdP should work, provided it can sign the full response.
1. **Create the Road application in your IdP.** Road's settings screen shows the **EntityID** and **Assertion Consumer Service (ACS) URL** to enter in the IdP. In return the IdP gives you a **Sign-in URL** and **Signing Certificate**, which you enter on the Road screen.
2. **Set the allowed email domains.** Only users from the domains you list may sign in over SAML.
3. **Sign the whole response.** Road requires the entire SAML response to be signed, not just the assertion; if only the assertion is signed, the sign-in is rejected.
4. **Optionally, disable password login.** Turn off email and password sign-in so everyone must come through the corporate IdP.
### Matching users
Road uses the SAML **`Subject.NameID`** as the user's unique identifier, and stores it on the User's **`externalId`**. Configure the IdP to send a stable, persistent value here, such as an internal user ID, rather than an email address, which can change.
### Provisioning
Road does not support SCIM. Users are onboarded in one of two ways:
- **Pre-provision over the API.** Create users ahead of time with their `externalId` set to the `Subject.NameID` the IdP will send. See [Providers, accounts and users](/docs/platform/account-management/providers-accounts-and-users).
- **Provision on first login.** With autoprovisioning enabled (it is off by default), Road creates the user the first time they sign in successfully.
When you autoprovision, you can also assign roles from SAML attributes. Road maps an IdP attribute to a role by matching on `equals`, `exists` or `contains`, so, for example, a Billing group and a Technical Administrator group can land on different Road roles, and new users get the right role without manual setup.
---
> Source: https://technology.road.io/docs/platform/account-management/single-sign-on/openid-connect
# OpenID Connect (OIDC)
Road supports OpenID Connect single sign-on, for both customer-facing identity and social login. OIDC is built on OAuth 2.0: an external identity provider (IdP) authenticates the user and issues a token that Road trusts. A business can connect its own customer IdP (WSO2, Auth0, Azure AD B2C), or let users sign in with Google, Apple, Facebook and other OIDC providers.
## How OIDC works
1. The user picks one of the configured OIDC providers.
2. Road redirects them to that provider.
3. They authenticate with the IdP (password, biometric, or multi-factor, whatever the IdP requires).
4. The IdP issues an ID Token describing the authenticated user.
5. Road verifies the token and reads the user's details, such as email and a unique identifier.
6. If the user already exists in Road they are signed in; otherwise a new user is created.
## OIDC on Road
Road's OIDC covers two cases:
- **Customer identity.** A business authenticates its customers against an external OIDC IdP, with each Account mapping its own IdP login to a Road user.
- **Social login.** Users sign in with an existing Google, Apple, Facebook or other OIDC account, so there is no separate Road password to manage, which suits consumer-facing products.
### Before you start
- The person configuring it has a **Provider (tenant) level Administrator** role.
- OIDC is **enabled at the Provider level** by a Road administrator.
OIDC is configured on the Provider's **Social login** page.
### Configuring a provider
You can enable **several OIDC providers at once**; each one shows as a **Log in with …** button on the sign-in screen, so users choose how to authenticate. To add a provider:
1. Supply its **OIDC issuer discovery URL** (the provider's `.well-known/openid-configuration`). Road reads the endpoints it needs from there.
2. Create an application on the provider's side for Road, and enter the **Client ID** and **Client Secret** it issues on the Road configuration page.
Road reads user details from the provider's UserInfo endpoint, so the application must grant three scopes:
- `openid`, for the user's unique identifier.
- `profile`, for basic details such as name.
- `email`, for the email address Road matches on.
Without those scopes Road cannot retrieve enough to match or create a user.
---
> Source: https://technology.road.io/docs/platform/account-management/custom-domains
# Custom domains
Private Label customers can serve the Road dashboard from their own domain, so the product stays on their brand. Road runs this on **Cloudflare custom hostnames**, which route the traffic and manage the TLS certificate for the domain, including renewal.
> Custom domains are set up during Private Label onboarding. Road configures the hostname on its side; the customer's IT team adds the DNS records, in the same zone as the chosen domain.
## How it fits together
The customer picks a domain (say `charge.northwind.example`) and Road configures a Cloudflare custom hostname pointing at the Road origin. The customer then adds two kinds of DNS record in their own zone:
1. A **CNAME** from the custom domain to the Road origin. The console shows the exact target; it is one of Road's dashboard origins, normally:
```
Type=CNAME Name=charge.northwind.example Value=dashboard.road.io
```
2. A **Domain Control Validation (DCV)** record, which proves control of the domain so Cloudflare can issue the certificate. The record is generated per domain and shown in the console; add it exactly as given. Its shape depends on the certificate:
- a **TXT** record for a standard certificate, or
- a **CNAME delegation** record (a target under `dcv.cloudflare.com`) for a wildcard certificate.
Once the records resolve, Cloudflare validates the domain, issues the certificate, and the domain serves over HTTPS. Renewal is automatic as long as the DCV record stays in place.
> **The DCV record's name and value are issued by Cloudflare for your specific domain and certificate type, so copy them from the console rather than from any example here. Leaving the record in place is what keeps renewal working.**
## Why DCV is required
Certificate authorities will only issue a certificate to someone who can prove they control the domain. The DCV record is that proof: it stops anyone else obtaining a certificate for a domain they do not own, and it lets Road automate issuance and renewal without further steps from the customer.
---
> Source: https://technology.road.io/docs/platform/account-management/data-migration
# Data migration
Moving onto Road from another system? There are three ways to bring an existing base of accounts, users, charging stations and cards across, depending on the size and shape of the job.
## Import by CSV
The dashboard has a staged CSV importer, **Data Imports**, at [https://dashboard.road.io/data-imports](https://{{customDNS}}/data-imports). Nothing is written until the whole set validates, so you fix problems against your source files rather than against half-migrated data.
It stitches the pieces together for you. You give each row a **`reference`** of your own, and rows point at their parents by it, a user names its `accountReference`, a connector its `evseControllerReference`, and so on. So you can hand it a pile of files in any order: it works out the graph, applies the objects in dependency order (accounts before the things that hang off them), and resolves every reference, whether the parent is another row in the same import or something already on Road. You never touch Road's internal IDs, and there is no right order to prepare your files in. This linking is the importer's own, separate from an object's `externalId`.
Validation runs across the whole set rather than file by file, so a location can point at an account that only exists as another row in the same import and still pass. It catches what actually breaks a migration: missing required fields, malformed emails, IBANs or country codes, connector ratings that do not add up, duplicate references, a parent that does not resolve, or a record that already exists. Every failure is pinned to its row and exportable, so you fix the source and run it again.
The import itself is resilient: records go in in batches, and a handful of bad rows do not sink the run, the good ones land and you are told exactly what went in and what did not. Files are plain CSV, comma or semicolon separated so European Excel exports work, up to 100 MB each.
It covers accounts, users, locations, charging stations, connectors, virtual cards and cards, and can either create records or update existing ones.
## Bring in the migration team
Migrating a large fleet of charging stations? Road has migration specialists who can plan and run a full migration with you. Most of the time this needs no physical access to the hardware; where it does, Road works with partners to get it done. Ask your account manager to bring them in.
## Migrate over the API
For a migration driven from your own tooling, the platform exposes create endpoints for the core objects. Start with [Accounts](/docs/platform/reference/platform-api/accounts) and [Users](/docs/platform/reference/platform-api/users), and see [Providers, accounts and users](/docs/platform/account-management/providers-accounts-and-users) for how provider-level provisioning works.
---
> Source: https://technology.road.io/docs/platform/account-management/billing
# Billing
Road bills **Accounts**, once a month. An Account can sit on either side of the ledger, or both: it can run charging stations as an operator, and it can hold charge cards for its users as an e-mobility customer. Which side a given charge falls on comes down to who owns the station and who owns the token that started the session.
## Tariffs and billing plans
A tariff and a billing plan are often mixed up. One covers what a charge costs, the other covers a recurring fee:
- A **tariff** is the cost of a *charging session*. It lives on the connector, through the EVSE controller's tariff profile, and turns the energy and time of a charge into an amount. Tariffs are covered under [charging station operation](/docs/platform/charge-point-operation/charge-stations).
- A **billing plan** is a *subscription*. A charge card carries one for its monthly or annual card fee, a charging station carries one for the operator's cost of running it on Road, and an [account tier](/docs/platform/account-management/providers-accounts-and-users) is set by one too.
## Who pays what
Billing splits three ways, by who is charged and for what.
### Charge card usage
When a user charges with a **card the Account owns**, the operating CPO reports the session cost, which reaches Road over roaming as a CDR, and Road bills it to the Account together with the subscription fee for each active card. One invoice goes to the Account no matter how many users or cards it holds, with charges grouped under the card that ran them so a billing administrator can apportion them. Operators can report a session long after it happened, up to around two years depending on the locality and the roaming agreement, so an invoice often carries sessions from earlier months. See [e-mobility sessions](/docs/platform/e-mobility-services/sessions).
### Running charging stations
When an Account **operates stations** on Road, its invoice covers the cost of running the infrastructure:
- subscription fees per charging station, from the billing plan,
- module costs, such as payment terminals, energy-management integration, or premium features, and
- settlement fees on payouts, including roaming-network fees for third-party e-mobility sessions and card-payment settlement fees for modes like tap-to-pay or scan-to-pay.
See [charging sessions](/docs/platform/charge-point-operation/charging-sessions) for the operator side of a charge.
### Reimbursement
Earnings flow the other way, as a **payout** to a station owner. The sessions a station runs are priced at its tariff, collected into a credit invoice, and paid out. Where the money lands depends on the site's reimbursement policy, set on its Location:
- for **public** stations, earnings usually go to the Account that owns the station,
- for **home-charging reimbursement**, the user owns the station and their employer reimburses them for their sessions, per the configured policy.
## Paying an invoice
Charges net out into a monthly invoice per Account. The Account's payment method decides how it is collected: **automatic**, where Road collects on the stored payment method, or **manual**, where the Account settles it itself.
---
> Source: https://technology.road.io/docs/platform/charge-point-operation/charge-stations
# Charging stations
A charging station is a physical box, installed at a site, with one or more plugs a driver connects to. The platform describes that with three resources:
- a **Location**, the site,
- an **EVSE Controller**, the station,
- and its **Connectors**, the individual plugs.
Each is a separate resource with its own configuration and status. The rest of this page takes them one at a time. If you integrate over OCPP or OCPI, the last section maps the platform's model onto those protocols, and shows where it deliberately differs.
## Location
A **Location** is the physical site: its address, its opening hours, and the one or more charging stations standing on it. A Location is owned by an Account.
Beyond the address, a Location carries two policies that decide how the site behaves.
**How the site earns.** The energy a station delivers is paid for, and the Location sets who receives that money:
- **business reimbursement**, paid to the Account that owns the site,
- **employee reimbursement**, where a driver owns the station (home charging, typically) and their employer reimburses them,
- **split reimbursement**, shared between the two,
- or **no reimbursement**.
**Whether the site is roamed.** A Location is either **published to the roaming network** or kept off it. A published Location is either **public**, visible to drivers across the connected networks, or **private**, reachable only by selected tokens.
## EVSE Controller
An **EVSE Controller** is the station itself: the unit that opens an **OCPP WebSocket** to the platform. It is where the live relationship with the hardware lives. It holds:
- the station's technical configuration,
- the **tariff** applied to a charge,
- and the **EVSE Commands** you send and receive over the API, to start or stop a session, reboot the station, or update its firmware.
Each EVSE Controller has an **eMI3 EVSE ID**, for example `NL*EFL*EV*1234567`: a country code, a party ID, then the station's identifier. The country and party ID are not fixed; they come from the partner's roaming-network configuration and differ from one operator to the next.
## Connector
A **Connector** is a single physical plug on the station: its socket type (Type 2, CCS, CHAdeMO), its power rating, and the details an API consumer needs to describe the connection point. An EVSE Controller has one or more Connectors.
## Coming from OCPP or OCPI
Skip this section unless you integrate over a protocol. The platform, OCPP and OCPI all describe the same hardware, but they draw the lines in different places.
The platform uses **two hardware levels**: an EVSE Controller with Connectors directly beneath it. OCPP and OCPI each use three levels, and the two three-level models are **not the same three**:
- **OCPP** is **Station → EVSE → Connector**. It has no idea of a site; it only knows the station it is connected to.
- **OCPI** is **Location → EVSE → Connector**. It has no idea of a station; a Location groups everything at a site.
In both protocols an **EVSE** is a single charging spot, the part that charges one car at a time. The platform does not give that middle tier an object of its own. Every physical connector hangs directly off the EVSE Controller. This is a hangover from OCPP 1.6, which had no EVSE tier either (just Charge Point → Connector).
> Despite the name, an EVSE Controller is **not** an OCPP or OCPI "EVSE". In those standards an EVSE is a single charging spot; here "EVSE Controller" means the whole station.
Two things follow from the flatter model. On the way in from OCPP 2.x, the EVSE tier is flattened away; with OCPP 1.6, which never had it, the fit is exact. And publishing to OCPI reverses the shape: OCPI shows each usable point to drivers as an EVSE, and the platform gives each Connector its own OCPI EVSE, with the controller's eMI3 ID suffixed per connector (`…*C1`, `…*C2`, …). A station with two EVSEs of two connectors each, held as one EVSE Controller and four Connectors, is published as one OCPI Location with four EVSEs.
The same hardware, named in each world:
| Real-world thing | Platform | OCPP 1.6 | OCPP 2.x | OCPI |
| ---------------- | ---------------- | ------------ | ---------------- | -------------------------------- |
| The site | Location | — | — | Location |
| The station | EVSE Controller | Charge Point | Charging Station | — (grouped into a Location) |
| A charging spot | *(not modelled)* | *(none)* | EVSE | EVSE |
| A physical plug | Connector | Connector | Connector | Connector (published as an EVSE) |
For the protocols themselves, see the [OCPP](/docs/emobility/ocpp) and [OCPI](/docs/emobility/ocpi) primer pages; for the roaming side, [publishing to roaming](/docs/platform/charge-point-operation/publishing-to-roaming).
---
> Source: https://technology.road.io/docs/platform/charge-point-operation/ocpp-connectivity
# OCPP connectivity
When ordering a charging station it often comes pre-configured with an existing OCPP backend. The OCPP backend provides connectivity to the Charging Station Management System (CSMS) and allows an operator to run and earn money.
When a provider such as E-Flux and other Road-powered CPOs are pre-configured on a charging station, there is no configuration needed.
In the case that you need to manually configure the OCPP settings for your charging station, please follow instructions below.
## Production environment
In order to connect to Road's OCPP backend the charging station will need configuration of the correct OCPP endpoint.
The required provider slug is given to you by your account representative, and it is different from the `Provider ID`. Set it under **Personalise** on this page and the endpoints below will show yours.
### OCPP 1.6 and OCPP 2.0.1
SSL (See below for more information):
```
wss://ocpp.road.io/{{providerSlug}}
```
Non-SSL connection (not recommended):
```
ws://ocpp.road.io/{{providerSlug}}
```
Sim card IPSec tunnel (requires VPN sim card):
```
ws://ocpp-internal.road.io/{{providerSlug}}
```
After a reboot, your charging station should show up in the [Charging stations view in your dashboard](https://{{customDNS}}/charging-stations).
> Debugging your connection
>
> You can do a plain HTTP call to http://ocpp.road.io/health to see if the OCPP server can be reached from the EVSE network.
## Connection security
Our OCPP backends are available via a Cloudflare protected SSL endpoint:
```
wss://ocpp.road.io/{{providerSlug}}
```
Note that certain hardware providers require extra steps to make an SSL connection work. This can involve hardware specific configuration or custom firmware versions that embed root Certificate Authorities.
### Root CAs
All operating systems manage a database of "Root Certificate Authorities" (Root CAs) that allow seamless connections to SSL endpoints.
If a hardware vendor does not keep a database of Root CAs, you may need to provide them with a set of Root CAs to establish an SSL connection.
The certificates served against our OCPP backend are currently signed by Google Trust Services (GTS). The Root CA certificates required to be trusted, which can be downloaded via , are:
- GTS Root R1 (RSA)
- GTS Root R2 (RSA)
- GTS Root R3 (ECDSA)
- GTS Root R4 (ECDSA)
- GlobalSign R4 (ECDSA)
> **Root CA limits**
>
> Some hardware vendors have restrictions on the quantity of Root CA certificates that can be initially installed, as well as supported public key types. If you face any such difficulties, prioritise installing GTS Root R1 and GTS Root R2 initially.
Let's Encrypt is used as a backup certificate issuer, in case there are any issues relating to certificate issuance via GTS. Therefore, it is recommended that the Root CA certificates relating to Let's Encrypt issued certificates are also installed. The Root CA certificates required to be trusted, which can be downloaded via , are:
- ISRG Root X1 (RSA)
- ISRG Root X2 (ECDSA)
### Private Connection
For hardware that cannot support SSL connections, we offer an alternative connectivity method based on mobile network configuration. This option, available exclusively with a Road-provided SIM card installed in the charging station, and allows for secure communication without SSL. This approach leverages a secure, private APN and IPSec tunnel to connect the charging station to Road's OCPP backend, ensuring safe data transmission within a controlled network environment.
To configure this connection, users should reach out to their account representative, who will provide the necessary setup instructions.
---
> Source: https://technology.road.io/docs/platform/charge-point-operation/onboarding-stations
# Onboarding stations
Onboarding is how a station gets from newly installed to live and charging. Along the way it gains an [EVSE Controller](/docs/platform/charge-point-operation/charge-stations) record, connects to the platform over [OCPP](/docs/platform/charge-point-operation/ocpp-connectivity), is configured, and is attached to an owner who prices it. That work usually follows installation, but the record can also be prepared in advance, ready for the moment the station connects.
## The lifecycle
A station's **operational status** has two levels: a master status the platform derives, and provider-defined statuses layered on top.
The master status is set from the station's own configuration:
- **Onboarding**: its connectors and OCPP settings are being configured. When that is done, the station progresses to Activation.
- **Activation**: an Account, a Location, a billing plan and pricing are set. When those are in place, it progresses to Live.
- **Live**: the station is operational and its subscription billing is running.
Two more master statuses come later: **out of service**, when a station is temporarily down but still billed, and **archived**, when it is retired. [Managing stations](/docs/platform/charge-point-operation/managing-stations) covers both.
A provider can also define statuses of its own and set them by hand, to match the way it works. A not-yet-live station might be tracked through in manufacture, in dispatch, on site and installed. Each of these maps onto a master status.
## The usual path: commission, then activate
An installer puts the station in the ground, wires it in and commissions it. For the station to reach the platform its OCPP connection must point there, which the manufacturer usually preconfigures; where they have not, the installer sets it. On the first connection the platform creates the EVSE Controller record, in Onboarding.
The installer then runs the **auto-configuration wizard**, which applies the station's OCPP settings by itself (connector count, websocket and connection, heartbeat, transactions, metering, vendor-specific options and local authorisation). The one manual step is confirming the physical connectors, their socket type and power rating. The station then progresses to Activation.
The customer then activates the station from the dashboard. They find it by OCPP identity or serial number and set its Account, Location, billing plan and [pricing](/docs/platform/charge-point-operation/tariffs-and-pricing). With those in place, the station goes Live.
## Pre-registration and bulk
You can register a station before its hardware arrives. Create the record with its OCPP identity and configuration ahead of time, one unit at a time through the [EVSE Controllers API](/docs/platform/reference/platform-api/evse-controllers), or a whole batch through a CSV import (also how an existing fleet is migrated, see [data migration](/docs/platform/account-management/data-migration)). A pre-registered station is already configured and priced, so on its first connection it goes straight to Live, with no setup left to do.
## What a station needs to charge
A station can take a charge once it:
- belongs to an **Account**,
- sits at a **Location**,
- has a **billing plan**, set as part of customer onboarding,
- is **priced**, through a tariff, an access group, or public free charging,
- and is **Live**.
Roaming is not set on the station. Whether it is on the roaming network, and whether it is public or private there, comes from its [Location](/docs/platform/charge-point-operation/charge-stations).
---
> Source: https://technology.road.io/docs/platform/charge-point-operation/tariffs-and-pricing
# Tariffs and pricing
A **tariff** is the price of a charging session. It attaches to a connector, either directly or through a shared [tariff profile](#tariff-profiles), and turns the energy and time of a charge into an amount the driver pays. This page is about how that price is built. For how the money then moves, see [billing](/docs/platform/account-management/billing).
## What a tariff is made of
A tariff combines one or more price components:
- **Energy**, per kWh delivered. The usual basis for a charge.
- **Time**, per hour connected. Used alongside energy or on its own.
- **Session fee**, a fixed amount per charge.
- **Idle fee**, per minute a vehicle stays plugged in after it has stopped drawing power, to keep a bay moving. It starts only after a grace period you set.
Tariff prices are net. VAT is not part of the tariff; it is added to each session at the rate that applies to the site, so one tariff bills correctly wherever it is used.
## AC and DC
A tariff prices **AC and DC charging separately**. Rather than banding by power, the platform splits on current type, so slow AC and fast DC covered by the same profile each get their own rates.
## Restrictions
A price component can carry **restrictions**, so it applies only in certain conditions instead of as one flat rate. A component can be limited to a time of day, a day of the week, a date range, or a session's duration or energy delivered. This is how an operator prices, say, evenings differently from daytime within a single tariff.
## Cost caps
A tariff can set a **maximum session cost**, and a ceiling on the **per-kWh price**. Once a session reaches the cap it stops being charged for. This protects a driver from a runaway bill, and it is what makes card payment safe where an amount is reserved on the card before charging starts.
## Scheduled tariffs
A **scheduled tariff** prices differently at different times of day, with nothing to switch by hand. It is not a future-dated change to the tariff. Instead the tariff holds several restricted components, and for each moment of a session the platform applies the component whose restrictions match, falling back to the unrestricted rate the rest of the time. A peak and an off-peak price, for example, live in one tariff as two time-restricted components.
## Dynamic pricing
A **dynamic tariff** follows the day-ahead energy market instead of a rate you maintain by hand. The platform tracks the wholesale spot price for the station's country (at present the Netherlands, Germany, Belgium, Austria and France) and prices each session against the market for the hours it ran. On top of the market price you set a margin, a fixed amount per kWh and a percentage, and a ceiling the per-kWh price never passes. When market data is unavailable, a backup rate applies.
## The Advanced Tariffs module
Scheduled tariffs, idle fees, dynamic pricing and cost caps belong to the **Advanced Tariffs** module. A provider enables the module, and then each capability is turned on per account, or by account tier, so an operator exposes only the pricing tools it needs. The basic components, energy, time and a session fee, do not need the module.
## Tariff profiles
A **tariff profile** is a named, reusable pricing definition owned by a provider and applied across many stations, so a change in one place reprices every station that uses it. A profile holds its AC and DC rates, and a station can either take the profile as it stands or override it with pricing set on its own connectors.
A profile can also price the same connector differently depending on who starts the session. It can carry variant rates for a particular card issuer, for a token provider, for the billing plan behind a mobility provider's token, or for a session started by scan-to-pay at the station. When a session starts, the platform picks the most specific variant that applies, and otherwise the profile's base rate.
## Who sets the price under reimbursement
A [Location](/docs/platform/charge-point-operation/charge-stations)'s reimbursement setting decides who is paid for the energy a station delivers, and with it, who sets the price.
- **Business reimbursement.** An operator or provider admin sets the full tariff, and the owning account is reimbursed for the energy delivered. **Split** reimbursement is the same tariff divided across several accounts.
- **Employee reimbursement** (home charging). The tariff is a plain per-kWh energy rate paid to the driver, with no time, session or idle fees. It is set through an invite: the employer either fixes the rate, or sets a maximum and lets the driver choose their own rate within it.
The reimbursement setting decides who is paid, not which tariff prices a session. The pricing itself is worked out the same way in every case.
## How a session's price is decided
When a session starts, the platform settles on one price and keeps it. It works through a fixed order and takes the first that applies: a maintenance session is free; free public charging overrides everything else; an access group can grant a free or custom rate to particular tokens; otherwise the connector's tariff applies, whether that comes from a tariff profile or from pricing set on the connector itself.
The resolved price is then stamped onto the session, and does not change if the tariff is edited later. This is why a session from months ago still shows the price that was in force when it ran. For a dynamic tariff, what is frozen is the formula, the market source, the margin and the cap, not a single figure, and the session is still priced against the market for the hours it actually ran.
## Publishing to the roaming network
Once a provider's roaming connection is live, a station's tariff is shared with roaming partners so their drivers see the price before they charge, with any roaming fee added to the shared copy. See [publishing to roaming](/docs/platform/charge-point-operation/publishing-to-roaming).
## Where this is heading: charging policies
**Charging policies** are the next step: a single object that holds price and access together. A policy carries a set of rules, each pairing a price with the group of tokens it applies to. When a driver presents a token the rules are evaluated in order, and the first that matches both authorises the session and prices it. Giving one card a free or cheaper rate is then a matter of adding a rule for its group above the open one.
Charging policies are set to replace tariffs and access groups over time. They are in preview today, available per provider and reachable through the [Charging Policies API](/docs/platform/reference/platform-api/charging-policies-beta), and for now they run alongside the tariff model described above rather than having replaced it.
---
> Source: https://technology.road.io/docs/platform/charge-point-operation/access-control
# Access control
Access control decides who may charge at a station and on what terms. A public station takes anyone who can pay; a depot takes only a fleet's own drivers. Underneath they are the same mechanism: which tokens are allowed on which connectors, and at what price.
## Access groups
An **access group** ties a set of tokens to a set of connectors. It can be attached to a whole station or to individual connectors, and connector-level groups take priority over station-level ones. Members are identified by their RFID card, by its uid or its printed visual number, or by the user who holds them.
A group is either open or **restricted**. On a restricted group only its members may start a charge on the connectors it covers, and a non-member is turned away. A group can also carry its own pricing, so a fleet or a staff cohort charges at its own rate: a member can be set to charge **free**, at a **custom** per-kWh rate, or at the standard tariff with access alone. Access groups are managed over the [EVSE Access Control API](/docs/platform/reference/platform-api/evse-access-control).
## Open, restricted and free
On top of its access groups, a station takes one of three postures, set by two switches:
- **Restricted** (neither switch on). Only the tokens in the station's access groups may charge. This is a depot or a private site.
- **Public.** The station also accepts cards from other networks, authorising them over [roaming](/docs/emobility/ocpi) against the driver's home provider. A token that belongs to no access group is allowed only when the station is public.
- **Free.** The station lets anyone charge with no authorisation at all. It suits a genuinely open, no-cost site, and it cannot be combined with public roaming charging.
## Tokens
A **token** is what identifies a driver to a station:
- **Your own tokens**, the RFID cards and fobs you issue. You assign them, group them, and block a lost one.
- **Roaming tokens**, carried by drivers from other networks. Each time one charges at your station the platform checks it live with the driver's home provider before allowing it, the [roaming](/docs/emobility/ocpi) side of access.
## How a charge is authorised
When a driver presents a token, the station asks the platform whether to allow the charge, and the platform answers in a single pass. It works through a few things in order: a station that is disabled or has no owner cannot charge; a free station accepts at once; a session started from a payment terminal or an app is matched to that; otherwise the token is judged on its own, against the connector's access groups and, for an outside card, its home network. The answer is allow, or refuse with a reason such as blocked, expired, or not permitted here. Every decision is recorded, so a refusal can be explained rather than guessed at.
## Charging through a connectivity gap
A charge should not fail because the network blips. When a station loses its connection it can still start a charge, and the platform takes those transactions in and settles them once the station is back in touch. Authorisation is deferred and reconciled rather than checked against a list held on the station, so a site keeps working through an outage instead of turning drivers away.
## Recognising the car
A station can start a charge from the car itself, with no card or app, by matching the identifier the vehicle presents over the cable (its EVCCID) to a token the platform already knows. A related path links a particular vehicle, a Tesla for instance, to an existing card, so that car autocharges wherever the card is accepted. Both are newer capabilities, offered in preview.
## Where this is heading: charging policies
Access is moving into the same **charging policies** that carry [pricing](/docs/platform/charge-point-operation/tariffs-and-pricing). There, drivers are gathered into **customer groups** (a named set of tokens, matched by account, user, card issuer and the like) and connectors into **charging groups**. A policy's rules then pair a customer group with a price: a rule applies only to the tokens in its group, and when it matches it both authorises the session and prices it. Access and price stop being two separate things.
Customer groups and charging groups are in per-provider preview, running alongside the access groups above rather than having replaced them, and existing access groups can be migrated across. They are reachable through the [Charging Groups](/docs/platform/reference/platform-api/charging-groups-beta) and [Customer Groups](/docs/platform/reference/platform-api/customer-groups-beta) APIs.
---
> Source: https://technology.road.io/docs/platform/charge-point-operation/charging-sessions
# Charging sessions
A **session** is the platform's record of a single charge. It ties together the pieces from the earlier pages: a token authorised at a connector on an EVSE Controller, owned by an Account, priced by a tariff, and turned into a billable record.
For the end-to-end flow across the protocols (plug in, authorise, meter, stop), see [anatomy of a charging session](/docs/emobility/anatomy-of-a-charging-session) in the primer. This page is about the session as a record.
## Two sides of one charge
A single charge is recorded twice, once from each side:
- a **CPO session**, the operator's record of a charge on its station, and
- an **MSP session**, the driver's provider's record of the same charge.
The two are separate records that hold different data: the CPO session carries the operator and OCPP side of the charge, the MSP session the driver and payment side. They are kept apart because the two sides are usually different companies. The **CPO session is the source**: when a charge finishes the operator turns it into a CDR, the billable record, and for a roaming charge pushes that CDR to the driver's network. The **MSP session is built from a received CDR**. The CDR is what connects the two. For how the CPO and eMSP sides fit together, see [the platform model](/docs/platform/introduction/overview).
The rest of this page describes the CPO session, the record an operator works with.
## What a session references
A session does not stand alone. It links back to:
- the **token** that authorised it,
- the **EVSE Controller**, **Connector** and **Location** it ran on,
- the **tariff** that priced it, and the resulting **cost** and **energy** delivered.
The token resolves to an **Account** and a driver only when it belongs to the platform. A roaming token belongs to another network, so the CPO session records it but the driver sits off-platform, and there is no matching MSP session on our side.
The cost is kept broken down, energy, time, a session fee and idle, alongside the total, in the session's currency and with VAT recorded against the rule that applied.
## Lifecycle
A session is not driven by a single status field. It opens when a token is authorised and a transaction starts on the station; it runs as meter values stream in over OCPP and cost is tracked against the tariff; it ends when the charge stops and the final energy and duration are known; and it is then settled into its billable figures. Whether a session is live, finished or settled is read from its timestamps and its billing state, not from one label.
## Valid, and billable
Two separate questions decide what becomes of a finished session, and the platform records each.
**Is it valid?** A session is marked invalid when its data cannot be trusted: implausible energy for the duration, an authorisation that was not accepted, a start or end time that makes no sense, or missing meter values.
**Is it reimbursed?** A perfectly valid session can still be left out of billing and reimbursement: a free, non-billable card, a session paid through an external solution, or one still waiting on an external funder to confirm before it can be included. That last case is left undecided on purpose until the funder answers.
Keeping the two apart matters for reconciliation, because a session that will not be billed is not necessarily a broken one. Either outcome is recorded with a reason, set automatically by the platform's integrity and billing checks or applied by hand.
### Reason codes
When a session is excluded, the reason is one of the following. A session can be excluded automatically or manually.
| Reason | Description |
| :---------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------ |
| `HISTORICAL_END` | The session started or ended on a past date that is no longer eligible for processing or billing. |
| `MAINTENANCE_TOKEN` | The session was started using a maintenance token. |
| `HIGH_ENERGY_USAGE` | The session consumed too much energy (kWh) for the given duration, likely incorrect meter data or a reporting error from the station. |
| `LOW_ENERGY_USAGE` | The session consumed too little energy for the given duration. |
| `LOW_DURATION` | The session duration is too short. |
| `IMPROBABLE_ENERGY_USAGE` | The calculated energy per minute is implausibly high for the given duration. |
| `NO_COST` | The session has no associated cost. |
| `AUTHORIZED_NON_BILLABLE_RFID` | The session was authorised by a non-billable card (an access-group member set to charge for free), so there is nothing to bill. |
| `EXTERNAL_PAYMENT_SOLUTION` | The session was started with an external payment solution. |
| `NON_ACCEPTED_AUTH` | The session began with an authorisation that was not accepted. |
| `MISSING_OR_INVALID_SESSION` | The session is missing or invalid. This can happen when we respond `Invalid` to an authorisation but the station charges anyway. |
| `MANUAL_EXCLUSION` | The session has been manually excluded. |
| `MANUAL_CANCELLATION` | The session was forcibly cancelled (for example, a `hard` cancellation). |
| `REPLACED_WITH_CORRECTED_SESSION` | The session was considered invalid and replaced with a corrected session (for example, cost recalculated). |
| `INVALID_START_TIME` | The start time of the session is invalid. |
| `INVALID_END_TIME` | The session's end time is invalid. |
| `UNKNOWN_METER_VALUES` | The meter values are unknown. |
| `TEMPORARILY_EXCLUDED_WAITING_FOR_MISSING_DATA` | The session is held out while it waits for missing data. |
| `NO_AUTO_REIMBURSEMENT` | The session cannot be reimbursed automatically because of insufficient payment information. |
| `EXTERNAL_PAYMENT_FAILED` | The session is not eligible for reimbursement because an external payment failed. |
| `EXTERNAL_REIMBURSEMENT` | The session is reimbursed through an external arrangement rather than by the platform. |
| `FAILED_TO_START` | The session failed to start. These are aborted sessions that never began properly. |
| `DUPLICATED` | The session is a duplicate of another valid session. |
| `UNSPECIFIED` | No specific reason was recorded. |
## From session to CDR
A settled session's authoritative billable form is a **Charge Detail Record (CDR)**: the final statement of what was delivered and what it cost. Billing runs on the CDR, not on the live session, and where a charge crosses networks the CDR is also what the operators exchange to settle with each other, the roaming side covered in the [roaming](/docs/emobility/ocpi) section.
A CDR can be repriced while it is still being processed, but once it has been billed its price is fixed, and a later change is made as a credit rather than an edit. This is why a session from months ago still shows the price that was in force when it ran.
---
> Source: https://technology.road.io/docs/platform/charge-point-operation/managing-stations
# Managing and maintaining stations
Once a station is live, the platform is where you run and look after it. Because a live station holds an open [OCPP connection](/docs/platform/charge-point-operation/ocpp-connectivity) to the platform, almost everything here happens remotely, with no one visiting the site: acting on a station, pushing firmware, pulling diagnostics, and seeing exactly what it has done.
## Remote actions
From a station you can:
- **reboot** it,
- **unlock a connector** to release a stuck cable,
- **stop a running session**,
- **change its availability**, so it stops accepting new charges while staying connected,
- and send any other OCPP command the station supports.
These act on the hardware over OCPP, so what a given station accepts depends on its firmware and OCPP version. See the [OCPP](/docs/emobility/ocpp) primer for the underlying commands.
Separately, an administrator can **ban** a misbehaving station. A ban is not an OCPP command; it blocks the station at the connection layer, so it can no longer connect at all until it is unbanned. The reason is recorded.
## Working across many stations
Doing any of that to a thousand stations by hand is not an option, so firmware pushes, configuration changes and arbitrary commands can be run across a selection as a **background job**. The job works through the stations on its own and reports the outcome for each one, so a failure on a single unit does not hold up the rest and you can see afterwards which took and which did not.
## Configuration and presets
A station's OCPP **configuration**, its settings exposed as configuration keys, can be read and changed from the platform, one station at a time or across many as a background job.
A **configuration preset** saves a set of those settings so a station, or a whole batch, can be brought to a known-good state in one step rather than key by key. Each provider keeps its own presets, and one can be marked as the default that is applied automatically the first time a new station connects. That default is the auto-configuration an installer relies on during [onboarding](/docs/platform/charge-point-operation/onboarding-stations).
## Firmware updates
You can push a firmware update to a single station, or roll one out across a fleet as a background job. The platform delivers the update over OCPP and tracks each station's progress as it downloads and installs, so you can see whether it took. Firmware behaviour is specific to the hardware, so what a model accepts, whether it needs a signed image, and how it reports progress depend on the vendor and its OCPP version.
## Diagnostics and health
When a station misbehaves you can ask it, from the platform, to upload its **diagnostics or logs**, and inspect them without anyone visiting the site. The platform also keeps a live view of each station's connectivity from its heartbeats and flags units that have gone quiet, and it raises problems it spots on its own, from lost connectivity to a faulted connector (see [charging station detected issues](/docs/platform/charge-point-operation/charging-station-detected-issues)). For uptime and availability over time, see [reliability reporting](/docs/platform/charge-point-operation/reliability-reporting).
## Command history
Every message between a station and the platform is recorded, and you can browse a station's full **command history**: what was sent, when, and how the station replied. You can filter by message type or status, hide the noise of routine heartbeats and meter values, narrow to a time range, and export the result. This is the first place to look when a station is doing something unexpected, because it shows exactly what passed between the two. From the same place you can issue a command by hand when you need to prod a specific unit.
## Maintenance access
A station can be handed to a **maintenance account**: a field-service or installer company that looks after the hardware without owning the station. A maintenance account sees only the stations it is responsible for, and from that view it can commission a unit, run the auto-configuration, manage the station's OCPP credentials, and test-charge with a **maintenance token**. A maintenance token charges for free, and only on a station that has not yet been claimed and billed, so an installer can prove a unit works before it is handed to its owner.
## Operational status
Every station carries an **operational status** that says where it is in its life. The platform advances a station through **onboarding**, **activation** and **live** on its own, as each stage is configured (see [onboarding](/docs/platform/charge-point-operation/onboarding-stations) for those first stages):
- **Onboarding**: the station is being set up, its connectors and OCPP settings configured.
- **Activation**: its commercial setup is being completed, an account, a location, a billing plan and pricing.
- **Live**: it is operational and charging, with its subscription billing running.
Beyond that, taking a station **out of service** (temporarily not charging, but still billed) or **archiving** it (retired, billing stops) is a deliberate step. A provider can also define **statuses of its own** to match how it runs its fleet, a unit awaiting a part for instance, each mapping onto one of these. A station's status governs whether it charges, is billed, and is offered to the roaming network, so it is the lever an operator uses to take a unit in and out of service.
---
> Source: https://technology.road.io/docs/platform/charge-point-operation/charging-station-detected-issues
# Charging station detected issues
The platform watches each charging station for faults, misconfigurations and communication problems that can affect charging, connectivity or reliability, and raises each one as a **detected issue** with a type and a severity. Open issues are shown in the dashboard and available through the [EVSE Issues API](/docs/platform/reference/platform-api/evse-issues).
## How issues are detected
Detection runs on a schedule, not the instant something happens on a station. Two checks run in the background: connectivity is checked every ten minutes, and every other kind of issue hourly. Each run looks at the station's current state and the last 24 hours of its activity, so an issue can take up to an hour to appear after the condition first arises, and up to the next run to clear once it is fixed.
Most thresholds are fixed, and several scale with the station's connector count (a four-socket station is allowed more traffic than a single). The one setting you control is the **connectivity window**: how long a station may be silent before it counts as offline, set per provider and 24 hours by default.
## Severity
Every issue carries a severity, **low**, **medium**, **high** or **critical**, fixed by its type. Severity drives how it is surfaced, and in particular whether it is emailed (see [notifications](#notifications)).
## The issues
| Issue | Severity | What it means |
| :------------------------------------ | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| No connectivity | Low | The station has not been in touch for longer than the provider's connectivity window (24 hours by default). |
| Socket error | Medium | A connector reports a fault, other than under-voltage or a power-meter failure, over `StatusNotification`. |
| Under voltage | High | A connector reports an under-voltage fault. |
| Power meter failure | High | A connector reports a power-meter failure. |
| Authorisation failures | Medium | The station's recent authorisations were refused repeatedly (four or more in a row) with no successful charge after them. |
| Concurrent transaction | Medium | A token was refused for trying to start a second session at the same time, a sign of possible misuse. |
| Command errors | Medium | Two or more OCPP commands returned errors in the last 24 hours. |
| Too many commands | Medium | The station sent far more messages than expected for its connector count in 24 hours. |
| Too many sessions | Critical | The station recorded far more sessions than plausible for its connector count in 24 hours. |
| Double OCPP identity | Critical | Another live station is using the same OCPP identity. |
| Double OCPP identity by serial number | Critical | Two hardware serial numbers have booted under one OCPP identity, a sign of a swapped or cloned unit. |
| Cost settings not configured | Medium | One or more connectors have no valid tariff. |
| Issues reported by the station | Medium | The station itself reported a fault it has not since cleared (`NotifyEvent` on OCPP 2, or a station-level fault on 1.6). |
| Transaction start point misconfigured | Medium | On OCPP 2, the station's `TxStartPoint` is set to a value the platform does not support (`ParkingBayOccupancy` or `Authorized`). |
| Invalid start date and time | Medium | The station started a session with a timestamp that is in the future, before the station existed, or implausibly old. |
| Invalid timestamp received | High | A start or stop message carried a timestamp the platform could not read. |
| Long connector ID | High | A connector ID is longer than nine characters. |
| Security policy violation | Critical | The station's connection does not meet its required security profile, usually the wrong credentials, or an insecure connection where a secure one is required. |
A few conditions have a name in the model but are not raised today: a boot loop, an idle fee incompatible with the EVSE's settings, and connectors that are not yet configured. Treat them as reserved rather than active.
## Notifications
The platform emails an issue when it is first raised. It is not a digest, and a recurring or long-running issue is not re-sent, so the dashboard is where you see what is currently open. Whether an email goes out, and to whom, depends on the issue:
- **Loss of connectivity** emails the station's owner, at most once every seven days per station. A provider can turn owner notifications off, and an account can turn them off or redirect them to its field-service contact.
- **Field-service faults** (loss of connectivity, under-voltage, a power-meter failure, and faults the station reports itself) email the station's **maintenance account**, when it has issue notifications enabled.
- **Critical issues** (a duplicated OCPP identity, an implausible number of sessions, a security-policy violation) email the provider's issue mailing list.
Everything else is shown in the dashboard and the API but sends no email. The recipients and toggles are settings on the provider (the issue mailing list, and whether owners are notified) and the account (whether to notify, and whether the owner or the field-service contact receives it).
## Resolving issues
An issue opens the first time its condition is detected and closes when the condition clears. Most issues **resolve themselves**: the next scheduled check sees the problem gone and marks the issue resolved. You can also resolve one by hand, from the dashboard or the API, but resolving an issue whose cause is still present only clears it until the next check finds it again. For a few types the platform stays quiet for 24 hours after a manual resolve, but the real fix is always to clear the underlying condition.
What that means in practice for the common ones:
- **No connectivity**: restore the station's link, its power, its mobile signal, or the backend address it connects to. It clears once the station is back in touch.
- **Socket error, under-voltage, power-meter failure**: a hardware fault at the connector. Clear it on site and the station reports itself healthy again.
- **Double OCPP identity**, or by serial number: two units are answering to one identity. Give each its own OCPP identity, or remove the stale duplicate.
- **Security policy violation**: bring the connection up to the station's required security profile, the right credentials and, where the profile demands it, an encrypted connection.
- **Cost settings not configured**: set a valid tariff on every connector.
- **Transaction start point, long connector ID, invalid timestamps**: correct the station's configuration, its start-point setting, its connector numbering, or its clock.
---
> Source: https://technology.road.io/docs/platform/charge-point-operation/reliability-reporting
# Reliability reporting
The platform records the status of every connector as it changes over time, and lets you export how long each one spent in each status over a period. That status history is the raw material for measuring availability, and for the reliability reports some operators have to file.
## What the report contains
The **reliability report** is a per-connector breakdown of time spent in each status over a date range, at **monthly or yearly** granularity, exported as CSV or JSON. Each row is one connector for one month or year, with the minutes it spent **available**, **charging**, **reserved**, **inoperative**, **out of order**, **blocked**, **planned**, **removed** or **unknown**, alongside the connector's station, location and account.
It covers whatever set of stations you select, so you can run it across the whole network or narrow to a site, a power type, or another slice. The report is per connector rather than a single network figure; a network or per-station view is that set of rows added up.
## How reliability is measured
The figures come from the connector status changes the platform already tracks. Each change is timed, and the time between one status and the next is credited to the earlier status, split across days where a status runs past midnight. A status still in force at the end of your date range is counted up to the end of the window. Because it is built from status data the platform holds anyway, there is nothing extra to instrument on the hardware.
## Turning it into an uptime figure
The report gives you **time in each status, not a single uptime percentage**. You choose which statuses count as "up" for your purpose, available and charging for instance, and work out the ratio against the total. This is deliberate: what counts as uptime, and what downtime is excused, differs between reporting regimes, so the platform provides the underlying time and leaves the definition to you.
## UK Public Charge Point Regulations
> **A common use case**
>
> Operators of public charging stations in the UK fall under the Public Charge Point Regulations 2023, which require rapid-charging operators to keep their network reliable on average across the year and to report that reliability annually. This report gives the per-station status time you build that submission from; you apply the regulation's own definitions and thresholds, which are maintained by the DfT. See the [Public Charge Point Regulations 2023](https://www.legislation.gov.uk/uksi/2023/1168) for the authoritative detail, as they can change.
## Running the report
You export the report from the charging stations list, over a date range and at monthly or yearly granularity. A short range returns straight away; a large one runs as a background export you download once it is ready. Reliability reporting is available to accounts with the analytics feature enabled.
---
> Source: https://technology.road.io/docs/platform/charge-point-operation/publishing-to-roaming
# Publishing to the roaming network
A station on Road can be made reachable to other charging networks, so drivers who belong to another provider can charge there and their charges settle back to you. This is the operator's side of [roaming](/docs/emobility/ocpi); this page is about how your stations get onto the network and the control you have over it.
## When stations are shared
Once your provider's roaming connection is live, the platform shares your eligible stations with the network on its own and keeps them in sync: change a station, its location or its tariff, and the update is pushed out. You do not publish each station by hand.
## What goes out
Two things are shared for each station:
- **The location and its connectors**, so partners know where it is and what it offers. Each connector is presented as its own network **EVSE** (the [charging stations](/docs/platform/charge-point-operation/charge-stations) page explains that mapping).
- **The tariff**, so a visiting driver sees the price before they charge. A roaming fee is added to the shared copy of the tariff; the tariff your own drivers pay is unchanged.
## Which stations are shared
A connector goes onto the roaming network only if it **accepts roaming charging**, that is, it takes cards from other networks (see [access control](/docs/platform/charge-point-operation/access-control)). A station that serves only your own tokens stays off the network. Roaming reach follows from how a station is set up to charge, not from a separate publishing step.
## Listed or unlisted
Each [Location](/docs/platform/charge-point-operation/charge-stations) has a **publishing mode** that controls how a shared station appears, not whether it is shared:
- **Public**: the station is listed on public maps, so any driver on an agreed network can discover it.
- **Private** (the default): the station is kept off public maps, so it is not searchable, but a driver who reaches it, from its ID or a direct link, can still charge there over roaming.
Private means unlisted, not hidden. A private station is still on the network and still usable; it just does not show up on a map. This suits a site you would rather not advertise but are happy for a known driver to use.
## Through Road's roaming hub
Your stations reach partners through Road's own roaming hub, which speaks the shared protocols out to other networks and to the large hubs such as Gireve and Hubject. You connect once, to Road, and never integrate with each partner or hub yourself.
## Where to go next
The mechanics of joining networks, the standards involved, and how charges settle between operators live in the [Roaming](/docs/roaming) section. If you are setting this up, start with [joining as a CPO](/docs/roaming/guides/joining-as-a-cpo).
---
> Source: https://technology.road.io/docs/platform/charge-point-operation/ere-integration
# ERE (Emissiereductie-eenheden)
> **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 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)
---
> Source: https://technology.road.io/docs/platform/e-mobility-services/cards-tokens-and-access
# Cards, tokens and access
A charge has to be authorised: the station needs to know that this driver may charge, and who will pay. On the platform that comes down to **tokens**, usually carried on a **charge card**.
## Token
A **Token** is the identifier that authorises a charging session. It is the thing an EVSE checks. A token carries:
- a **UID**, the RFID value encoded on a card or tag, read when it is tapped,
- a **contract ID**, the identifier (an OCPI/eMA-ID) that ties the token to its eMSP so authorisation requests route to the right place,
- a **visual number**, the human-readable number printed on the card.
When a card is tapped, the station reads the UID and sends an **Authorize** request. If the platform recognises the token and it is valid, the charge is allowed to start. Tokens are also what make roaming work: they are shared with other operators so a driver can charge off-network, which the [roaming](/docs/emobility/ocpi) section covers.
## Charge card
A **Charge Card** and a **Token** are separate things: the card is what the driver holds, and the token is the identifier attached to it that actually authorises charging. Every card carries a token, but a token can exist without a card.
- **Physical cards and tags** are RFID cards or key fobs. They ship pre-encoded with a UID; ordering one takes a matching token from stock, binds it to the driver's account, and posts the card out.
- **Virtual cards** are a token with no physical card. Use one to onboard an RFID card a driver already holds, or for app-based charging where no card is needed.
## Where a card can charge
Two things decide where a driver's card works, and neither is set on the card itself.
**Roaming reach.** A card can charge anywhere the platform reaches over the roaming network. That is the default extent of where it works.
**Charging restrictions.** On top of that, an eMSP can narrow where and when a driver may charge, with rules that apply as the charge is authorised:
- a **schedule**, allowing charging only on certain days and times,
- a set of **allowed countries**,
- and **networks** to allow or block, by operator.
Restrictions are grouped and inherited: a driver takes the rules of their charging-restriction group, or their fleet's default, or the account's. This is a newer capability, currently in preview.
Do not confuse this with **[access control](/docs/platform/charge-point-operation/access-control)** on the operator side. That is a station operator deciding who may charge at *its* stations; charging restrictions are an eMSP deciding where *its* drivers may charge.
---
> Source: https://technology.road.io/docs/platform/e-mobility-services/dispatching-cards
# Dispatching cards
An eMSP has two ways to give a driver a card:
1. a physical, branded card, or
2. a virtual card, which also covers turning an RFID card the driver already holds into a means of payment.
## Virtual cards
A virtual card is a token with no physical card. The platform creates the token, which can carry the UID of an RFID card the driver already has, and makes it available over the roaming network, so that existing card can pay for charging across the connected networks. A virtual card is usable at once, with nothing to post or activate.
A typical flow over the API:
- create the accounts and users ([POST /1/users](/docs/platform/reference/platform-api/users#postv1users), [POST /1/accounts](/docs/platform/reference/platform-api/accounts#postv1accounts)),
- create the virtual cards ([POST /1/cards/virtual](/docs/platform/reference/platform-api/cards#postv1cardsvirtual)),
- list an account's cards ([POST /1/cards/search/fast](/docs/platform/reference/platform-api/cards#postv1cardssearchfast)),
- block and unblock a card ([POST /1/cards/:card/disable](/docs/platform/reference/platform-api/cards#postv1cardsbycarddisable) and [POST /1/cards/:card/enable](/docs/platform/reference/platform-api/cards#postv1cardsbycardenable)),
- pull the driver's charge sessions ([POST /1/sessions/search](/docs/platform/reference/platform-api/msp-sessions#postv1sessionssearch)).
## Physical cards
Road can supply a fully white-labelled physical card, and run the volume creation, activation and posting on your behalf. Speak to your account manager to set this up.
A physical card moves through a short lifecycle:
1. **Ordered** for a driver, and paid for where it is a self-service order.
2. **Assigned a token.** Cards are held pre-encoded with an RFID UID; the platform takes one from stock, binds it to the driver's account, and mints its contract ID.
3. **Sent** to the driver.
4. **Activated**, and ready to charge.
---
> Source: https://technology.road.io/docs/platform/e-mobility-services/finding-locations
# Finding charging locations
Your drivers can charge at your own stations and at every location Road reaches through [roaming](/docs/roaming). The **Road map widget** is an embeddable JavaScript library that puts that coverage on your own site, so drivers can see and search the locations open to them. A live preview is at [mapsdk.road.io](https://mapsdk.road.io/).
The same location data is available over the [Map API](/docs/platform/reference/map-api) if you would rather build your own interface.
## Embedding
To embed the map, the below widget needs to be pasted into the HTML of the website, the configuration parameters are explained in a section below. For the widget to draw properly, the container DOM node needs to have height specified for the widget to fill its full height.
```html
```
### Configuration
The configuration dictionary assigned to `window.ROADIO_MAP_SDK` supports following keys:
**Starting from version 2, a public key must be provided when initialising the widget.** Please contact your account manager to get an authorisation key.
| Field Name | Required | Version(s) | Context | Description | Default Value |
| :-------------------------- | :------- | :--------- | :------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------- |
| `version` | No | Both | Both | Which version of the map SDK do you wish to use. | `v1` |
| `domNode` | Yes | Both | Both | Reference to a valid DOM element where the map will be rendered. | `-` |
| `gestureHandling` | No | Both | Both | Google Maps SDK parameter controlling how gestures (scroll, pinch, etc.) interact with the map. Allowed values: `greedy`, `cooperative`, `none`, `auto`. We suggest a default value of `greedy`. | `-` |
| `language` | No | Both | Both | Language of the widget. Supported values: `en` | `en` |
| `position {lat,lng}` | No | Both | Both | Initial latitude/longitude to centre the map on load. If not provided, defaults to visitor's IP-based location. | Visitor IP location |
| `enableZoomControls` | No | Both | Both | Whether to display zoom (+/-) controls in the bottom-right corner. | `false` |
| `enableSidebar` | No | Both | Both | Enables sidebar with location/cluster information when selected. | `false` |
| `enablePlacesSearch` | No | Both | Both | Enables address search input at the top of the map. | `true` |
| `provider` | Yes | Both | Both | Provider reference within the Road platform. | `-` |
| `locationId` | No | Both | Both | If provided, the map will zoom to the given `locationId` and open its sidebar (if enabled). Overrides `position`. | `-` |
| `theming {}` | No | Both | Both | Controls widget look and feel with keys: `brandColor`, `backgroundColor`, `textColor`, `borderColor`. Accepts HEX values (e.g. `#ff3d00`). | `-` |
| `authorization` | Yes | v2 | Both | The public authorisation key to search for locations. | `-` |
| `limitToLocationIds` | No | v2 | Both | Constrains the map to only show the locations with the configured IDs. | All available locations |
| `preferredOperators` | No | v2 | Both | Ensures that the selected operator appear at the top of the operator selector dropdown | |
| `showNoAdditionalFeeFilter` | No | v2 | msp | Used to configure whether we enable the user to filter out stations that come with additional fees. | |
| `showPublishingModeFilter` | No | v2 | cpo | Used to configure whether we enable the user to filter out public or private stations. | |
| `showOperatorOnly` | No | v2 | both | Include a quick filter option to find a specific operator. | |
Full example:
```javascript
window.ROADIO_MAP_SDK = {
domNode: document.getElementById("async-road-embed-map"),
gestureHandling: 'greedy',
language: 'en',
position: {
lat: 52.377956,
lng: 4.897070
},
enableZoomControls: true,
preferredOperators: ['E-Flux', 'Road B.V'],
showOperatorOnly: 'E-Flux',
authorization: '8921ad68-727d-4f95-ae8a-7a738371417b',
version: 'v2',
enableSidebar: true,
enablePlacesSearch: true,
}
```
### React
A `React` component is made available to make it even easier to embed the `Road` map into your existing application. You can find the documentation for the component [here](https://www.npmjs.com/package/@road-labs/map-sdk).
Example usage of the component:
```typescript
import React from 'react';
import { App } from '@road-labs/map-sdk';
import '@road-labs/map-sdk/dist/index.css';
const MyMap: React.FC<{}> = (props) => {
return (
);
}
```
---
> Source: https://technology.road.io/docs/platform/e-mobility-services/sessions
# Sessions
An eMSP session is the record of a charge one of your drivers ran, and the basis for billing them for it. It is the mobility-side counterpart to the operator's [CPO session](/docs/platform/charge-point-operation/charging-sessions).
## Where a session comes from
A driver charges, on your own network or on someone else's. The operating CPO writes the charge up and sends it out over roaming as a **CDR** (Charge Detail Record). When a CDR reaches Road for one of your tokens, whether from Road's own CPO side or from an external operator, it becomes an eMSP session on your side. Sessions are created from those returning CDRs, not written at the station.
A CDR can arrive well after the charge, sometimes weeks or months later, so an eMSP session can appear, and land on an invoice, long after the driver actually charged.
## What the driver pays
A session is priced by the **billing plan** on the token that charged. That plan decides how the driver or fleet is charged for the energy and time on the session, and it is what the eMSP invoice is built from.
## Corrections
Sessions are not edited after the fact. A correction or refund is booked as a [credit session](/docs/platform/e-mobility-services/credit-sessions) against the original.
## In the API
eMSP sessions are served from the [MSP sessions](/docs/platform/reference/platform-api/msp-sessions) endpoints, and corrections come through [MSP credit sessions](/docs/platform/reference/platform-api/msp-credit-sessions).
---
> Source: https://technology.road.io/docs/platform/e-mobility-services/credit-sessions
# Credit sessions
This page is for customers who process [eMSP sessions](/docs/platform/e-mobility-services/sessions) for billing, in particular private-label customers running their own billing.
A **credit session** is a durable record of a price correction to an eMSP session. When a roaming partner withdraws or corrects a CDR it delivered earlier, the platform creates a credit session that captures the amount before and after the correction, and links it to the original session.
## When a credit session is created
- A roaming partner sends a **credit CDR** (OCPI `credit=true`) withdrawing a CDR it delivered earlier.
- Road applies a **manual correction or exclusion** to a session, for example following a data-quality dispute.
In both cases:
- A credit session is created recording the correction.
- The original eMSP session references it via `creditSessionId`.
- The original session's own fields (`kwh`, `externalCalculatedPrice`, timestamps) are left untouched.
- No new eMSP session is created for the credit CDR.
A session references at most one credit session.
## Deprecation of the `excluded` flag
Previously, a correction or exclusion set `excluded=true` and an `excludedReason` on the eMSP session itself, applying the change in place. Credit sessions replace this with a discrete record for each correction, so you can see how a session's amounts changed and when.
The `excluded` flag is now **deprecated** for corrections and exclusions. When a credit session is created, the original session is not marked `excluded=true`; it is linked via `creditSessionId` instead.
> **Historical data**
>
> Sessions excluded before this change keep their `excluded=true` and `excludedReason` values, so continue to honour the flag when processing historical data.
## What a credit session carries
Each correction is a discrete record with its own timestamps, so the before and after of every price change is preserved and auditable. For billing, three fields matter: `correctionType` (`full-refund` or `partial-refund`), `totalAmount` (the delta to apply, negative when money is owed back to the customer), and `sessionId` (the session it corrects). The record also links back to the original CDR and carries the original session's provider, account, end time and energy.
For the full object, see [MSP credit sessions](/docs/platform/reference/platform-api/msp-credit-sessions) in the API reference.
## Retrieving credit sessions
Use [POST /1/credit-sessions/search](/docs/platform/reference/platform-api/msp-credit-sessions#postv1creditsessionssearch) to retrieve credit sessions. It requires the `creditSessions:read` permission and follows the standard search conventions: `from`/`to` time-range filtering, `skip`/`limit` pagination, and sorting on `createdAt` (descending by default).
The [POST /1/sessions/search](/docs/platform/reference/platform-api/msp-sessions#postv1sessionssearch) endpoint accepts two related flags:
- `includeCreditSessions: true` embeds each session's credit sessions as a `creditSessions` array.
- `excludeCreditedSessions: true` filters out sessions that have a credit session, which is useful when exporting sessions for billing.
## Handling corrections in your billing
- Poll credit sessions on a `createdAt` time window, using the same batch practices as for eMSP sessions.
- Apply `totalAmount` as the correction. A negative value means the customer is owed money back.
- If the original session was already invoiced, issue a credit note for `totalAmount`.
- Corrections can arrive well after the original session was delivered, so treat them with the same delayed-data strategy as [delayed CDRs](/docs/platform/reference/platform-api/msp-sessions).
---
> Source: https://technology.road.io/docs/platform/e-mobility-services/credit-limits
# Credit limits
An eMSP carries the cost of a charge before it bills the driver: the driver charges now and pays later. A **credit limit** caps how much they can run up in a month before that catches up. When the limit is reached, the driver's cards stop working until it clears. The feature is off by default and enabled per provider.
## How the limit is set
A limit applies at two levels, the **account** and the individual **user**, and both are checked. It is set automatically from the account's **trust tier**: a new, lightly verified account gets a lower limit, and the limit rises as the account is verified (contact and billing email, phone, bank details, VAT number). A limit can be **unlimited**, which is the default, or **zero**, meaning no credit at all.
## What counts towards it
The limit is measured against the **current calendar month's charging spend**: the cost of the e-mobility sessions the account's drivers have run this month, VAT included, in the account's billing currency. It is a monthly figure, not a running balance since the last invoice.
Sessions paid for at the charging station (immediate payment) do not count towards it, and are handled separately (see below).
## What happens at the limit
The platform warns by email at **80%** and again at **100%** of the limit. Once spend reaches the limit, the account's tokens are **blocked**: a charge is refused with "Credit limit exceeded", whether the driver taps a card, starts from the app, or charges on another network over roaming.
Because the account limit and the user limit both apply, a token is blocked when **either** is exceeded, and clears only once **both** are back under.
## How it resets
The figure is monthly, so it **resets at the start of each calendar month**: last month's spend stops counting, and blocked tokens are freed once the account and user are back under their limits. Raising an account's limit, for example when a trust-tier upgrade lands, also brings it back under and unblocks. It is not tied to when an invoice is paid.
A single token can also be blocked or unblocked by hand through the [tokens API](/docs/platform/reference/platform-api/tokens), though the monthly check may set it again on the next charge.
## Immediate payment
Some billing plans take payment at the charging station rather than on credit. For those, an account is blocked from starting another such charge while it has unpaid immediate-payment sessions outstanding, and is freed once they are settled. This is separate from the monthly credit limit, and those sessions do not count towards it.
---
> Source: https://technology.road.io/docs/platform/e-mobility-services/fleet-management
# Fleet management
**Fleets** let an account split its drivers, cards and vehicles into groups and hand the running of each group to a fleet manager, without giving that manager control of the whole account. This suits a company that runs charging for its staff and wants each team or site managed on its own. Fleet management is a newer capability, currently in preview and enabled per provider.
## Fleets and accounts
A **fleet** is a grouping inside a single [account](/docs/platform/account-management/providers-accounts-and-users), and an account can hold several. It is an administrative scope, not a billing entity: everything financial, billing plans, [credit limits](/docs/platform/e-mobility-services/credit-limits) and reimbursement, stays at the account and token level. A fleet carries only its identity, its membership, and its own charging rules.
## Members and assets
- **Drivers** belong to one fleet at a time, and their [cards and tokens](/docs/platform/e-mobility-services/cards-tokens-and-access) travel with them: assign a driver to a fleet and their tokens move too; remove them and the tokens leave with them, still assigned.
- **Vehicles** are the fleet's own assets. A vehicle is registered in the fleet and assigned to a driver, one per driver, and when a driver leaves it stays behind with the fleet.
## Managing a fleet
Management rights are granted per fleet, separately from membership, in two roles:
- a **fleet manager**, who runs the fleet: inviting drivers, issuing and assigning cards and tokens (including requesting a batch of new ones), registering and assigning vehicles, setting charging rules and seeing usage,
- and a **fleet observer**, the read-only version.
Grants are given and revoked by the account's or provider's administrators. A manager's reach is limited to the fleets they hold: no account-wide or provider-wide access, and no ability to grant rights to anyone else.
## Charging rules
A fleet sets where and when its drivers may charge through [charging restrictions](/docs/platform/e-mobility-services/cards-tokens-and-access#where-a-card-can-charge). It can carry a **default restriction** for all its members, and gather members into **restriction groups** for tighter rules on some of them, where a driver's group takes precedence over the fleet default.
One thing to watch: a fleet's rules stand on their own. If a fleet sets no default restriction, its members are **unrestricted**; the account's own default does not apply to a driver who is in a fleet. That is deliberate, so a fleet manager can genuinely say "no restriction" for their fleet.
## Usage and sessions
A fleet has its own view of what it has run: its [sessions](/docs/platform/e-mobility-services/sessions) can be searched and exported, and a usage overview breaks spend down over time, by driver and by vehicle, in the account's currency. Each is scoped to the fleet, so a manager sees their fleet and no further.
---
> Source: https://technology.road.io/docs/platform/payment-methods/overview
# Payment methods
A driver can pay for a charge on Road in several ways, and an operator can offer as many of them as suits the site. They split into two kinds: drivers who have an account or a card, and drivers who just want to tap and pay.
- **[Charge cards and the roaming network](/docs/platform/payment-methods/charge-cards-and-roaming)** — a driver with an RFID card or a contract, including drivers from other networks, charging on the strength of who they are and settling later.
- **[On-station card terminals](/docs/platform/payment-methods/on-station-terminals)** — a payment terminal at the charging station, so anyone can tap a bank card, no app and no account.
- **[Web checkout](/docs/platform/payment-methods/web-checkout)** — scan a QR at the charging station and pay on a web page, no hardware to install.
- **[In the mobile app](/docs/platform/payment-methods/in-the-mobile-app)** — a stored card, wallet or linked charge card in the Road mobile app.
## Ad-hoc payment and the law
The card terminal and web checkout methods matter for more than convenience: they are how an operator meets the rules on **ad-hoc payment** at public charging stations. A public charging station has to let a driver pay there and then, without signing up to anything.
- In the EU, this is the **Alternative Fuels Infrastructure Regulation (AFIR)**: public stations opened after 14 April 2024 must offer a means of anonymous payment for a charge.
- In the UK, the **Public Charge Point Regulations 2023** carry a similar contactless-payment duty (alongside the [reliability reporting](/docs/platform/charge-point-operation/reliability-reporting) duty).
An [on-station terminal](/docs/platform/payment-methods/on-station-terminals) or [web checkout](/docs/platform/payment-methods/web-checkout) is the regulated ad-hoc path in both cases, so switching one on is how an operator meets the obligation.
## How each method settles
Where the money ends up differs by method:
- **Charge cards and roaming** settle through billing: the charge is collected and, for a visiting driver, reconciled between the two networks. See [Billing](/docs/platform/account-management/billing).
- **On-station terminals** settle at the terminal, outside roaming and mobility-provider billing, so they are not invoiced again.
- **Web checkout and mobile** take payment from the driver's card through a payment provider, reserving an amount up front and taking the final cost when the charge ends.
---
> Source: https://technology.road.io/docs/platform/payment-methods/charge-cards-and-roaming
# Charge cards and the roaming network
The oldest and most common way to pay for a charge is not to pay at the charging station at all: a driver identifies themselves with a **charge card**, an RFID card or fob, or a contract linked to an app, and the cost is billed to their account afterwards. The station does not take a payment; it checks that the driver is allowed to charge and lets them.
## On your own network
When a driver charges with a card issued for your own stations, the platform authorises them against the [access rules](/docs/platform/charge-point-operation/access-control) on that connector and records the session. The cost is settled through [billing](/docs/platform/account-management/billing) rather than taken at the point of charge.
## Across the roaming network
The reach of this method comes from **roaming**. A driver whose card belongs to another network can still charge at your stations, and your drivers can charge on any network Road has an agreement with. When a visiting driver presents their card, the platform checks with the driver's own provider in real time before allowing the charge, then issues the charge record to that provider to settle between the two networks.
For the driver, one card or one app works across a large network of charging stations without a separate account for each operator. For the operator, a station is not limited to its own customers.
The mechanics of joining networks, real-time authorisation and settlement between operators are covered in the [Roaming](/docs/roaming) section. The tokens themselves are managed over the [Tokens API](/docs/platform/reference/platform-api/tokens).
---
> Source: https://technology.road.io/docs/platform/payment-methods/on-station-terminals
# On-station card terminals
A **payment terminal** at the charging station lets any driver pay with a contactless or inserted bank card, with no app, no account and no charge card. It is the most direct way to pay, and the regulated ad-hoc payment path for [AFIR and the UK regulations](/docs/platform/payment-methods).
## How it works for the driver
Paying happens entirely at the charging station:
1. Plug in the car.
2. Tap or insert a card at the terminal. It shows the price and reserves an amount on the card.
3. The charge runs.
4. Unplug. The session ends, the final cost is taken, and a receipt is available.
The platform runs the charge as it normally would, and does not re-check the payment, because the card was authorised at the terminal. Road watches the running cost and stops the session if it reaches the reserved amount.
## Two kinds of terminal
A terminal connects to the platform in one of two ways:
- **Embedded in the station.** The card reader is built into the charging station and talks to the platform over **OCPP**. Road supports a wide range of these, across hardware vendors such as Alpitronic, ABB, EVBox and Sicharge, among others.
- **Standalone.** A separate terminal at the bay that supports **OCPI**, such as Payter and Nayax, connected to the platform over the roaming interface.
Either way, a terminal is linked to a whole station or to specific connectors, and managed over the [Payment Terminals API](/docs/platform/reference/platform-api/payment-terminals).
## Who acquires the payment
Taking a card payment needs a merchant acquirer, and Road handles that, so an operator does not need its own. A terminal runs under one of two arrangements:
- **Road as merchant.** Road takes the card payment through one of its acquirers and reimburses the operator for the energy through platform billing. Road holds acquiring contracts with several acquirers (Elavon, Worldline, Payone and Adyen) and supports a range of terminal models across them.
- **The operator as merchant.** The operator brings its own acquiring, so the card funds land with it directly. Road links the terminal and exchanges the charge data, but stays out of the money and does not reimburse.
## How it settles
Terminal payments are taken at the terminal and settle outside roaming and mobility-provider billing, so a terminal session is never invoiced again through another party.
> **Setup**
>
> On-station payment is off by default and switched on per operator, for a whole station or specific connectors. The terminal's own settings, such as the amount it reserves, live in its configuration, and Road handles linking it to the station and passing the charge data across.
---
> Source: https://technology.road.io/docs/platform/payment-methods/web-checkout
# Web checkout
Web checkout, or **Scan-to-Pay**, gives a driver ad-hoc card payment without a payment terminal on the charging station. A QR code or short station code sends the driver to a hosted web page where they pick a connector and pay by card. It is the software equivalent of an [on-station terminal](/docs/platform/payment-methods/on-station-terminals): the same regulated ad-hoc payment, with no hardware to install.
## How it works for the driver
The driver scans the code on the charging station, lands on the station's page, chooses the connector, and pays. Payment is handled in a hosted card form, so card details go straight to the payment provider and never touch the operator's or Road's systems. The form accepts a card, Apple Pay or Google Pay. It reserves an amount up front, the charge runs, and the exact cost is taken when the session ends, with a receipt shown afterwards.
Because there is no hardware, an operator can offer ad-hoc payment on stations that were never fitted with a terminal, which is often the quickest way to bring an existing fleet into line with the [ad-hoc payment rules](/docs/platform/payment-methods).
## Whitelabelling
The checkout page carries the operator's branding and runs in the driver's language, so it reads as part of the operator's own product rather than a Road page.
## How it settles
Payment is taken from the driver's card by the payment provider: an amount is reserved when the charge starts and the final cost is captured when it ends. The funds settle to the operator.
---
> Source: https://technology.road.io/docs/platform/payment-methods/in-the-mobile-app
# In the mobile app
A driver who charges through the Road mobile app pays with a payment method held in their account, so starting a charge and paying for it are one action. Unlike [web checkout](/docs/platform/payment-methods/web-checkout) or an [on-station terminal](/docs/platform/payment-methods/on-station-terminals), this is not ad-hoc: the driver has an account, and the app remembers how they pay.
## What a driver can pay with
The app offers three ways to pay, chosen from one place before a charge:
- **A stored card**, added to the account and used for any charge.
- **A wallet**, Apple Pay or Google Pay, held the same way as a card.
- **A linked charge card**, the account's own RFID card or contract, where the charge is billed to the account rather than taken from a card.
The app never handles raw card details; they are held by the payment provider, and the app works with a reference to the stored method.
## Reserving and taking payment
For a card or wallet, the app checks a **pre-authorisation limit for the connector** before the charge and reserves that amount, so there is always enough headroom for the session. The [session cost cap](/docs/platform/charge-point-operation/tariffs-and-pricing) keeps the real cost within it. When the charge ends, the final amount is taken and the reservation released. A card may need a one-off security check (3-D Secure) the first time it is used. A charge paid with a linked charge card carries no reservation, because it settles through the account.
## No ad-hoc payment in the app
The app is for account holders, so it does not offer one-off guest payment. A driver who wants to pay without an account uses an [on-station terminal](/docs/platform/payment-methods/on-station-terminals) or [web checkout](/docs/platform/payment-methods/web-checkout) instead.
---
> Source: https://technology.road.io/docs/platform/integrations/marketplace
# Application Marketplace
> **Preview / beta**
>
> The Application Marketplace is under active development. The APIs, schemas and dashboard surfaces described here are not yet considered stable, and breaking changes may occur without long deprecation windows. If you are integrating today, please coordinate with Road so we can keep you informed of changes that affect you.
The Road Application Marketplace lets a third-party application connect to Road on a customer's behalf, read their charging data, and build on top of the platform. The application authorises against a provider, the customer consents to a set of scopes, and from then on the application calls the Road API with an access token issued for that customer.
The protocol is plain OAuth 2.1 and OpenID Connect: Authorisation Code flow with PKCE, standard discovery, token, userinfo and JWKS endpoints, and RS256-signed ID tokens. There is nothing Road-specific to implement, so an off-the-shelf OAuth/OIDC client library will do. [Authenticating your application](/docs/platform/integrations/marketplace/authentication) covers discovery, the flow and token exchange in full.
## How this documentation is organised
This page explains the concept. The following pages cover each part of the integration in detail:
- [Registering an application](/docs/platform/integrations/marketplace/registration) covers the three ways to obtain a client (curated templates, provider installations, and dynamic self-registration) and how to get set up.
- [Authenticating your application](/docs/platform/integrations/marketplace/authentication) covers the OAuth flow itself: discovery, public vs confidential clients, PKCE, the authorisation request, and token exchange.
- [Consent, grants and data sharing](/docs/platform/integrations/marketplace/consent-and-sharing) covers what the customer consents to, how they manage connected applications, and how they choose which data to share.
- [Using the API](/docs/platform/integrations/marketplace/using-the-api) covers calling the API with an access token, refreshing tokens, and handling errors.
- [ERE integration](/docs/platform/charge-point-operation/ere-integration) covers reading charging data for Emission Reduction Units end to end.
## Enabled per provider
The marketplace is made up of three independent capabilities, each enabled separately per provider in the provider's configuration:
- **Curated templates** lets a provider install Road-vetted catalogue applications.
- **Provider installations** lets a provider define and register their own applications.
- **Dynamic registration** lets applications self-register against the provider (RFC 7591).
A provider may have any combination of these switched on. If the capability you need is not available for the provider you are integrating with, it has not been enabled. To request access, contact us at .
## Still in preview
The marketplace is being actively extended. Expect additional scopes and related functionality to be added over time. The data-sharing model is intentionally generic so that new scoped APIs can adopt it as they land. Treat the current surface as a preview and check back for changes.
## Reference application
> Reference implementation
>
> A working Next.js application exercising the full flow (PKCE, token exchange, refresh, and ERE reads) is published at [github.com/e-flux-platform/example-3rd-party-marketplace-app](https://github.com/e-flux-platform/example-3rd-party-marketplace-app). The repository README has the context needed to get started. You will need credentials and the provider's discovery URL; see [Registering an application](/docs/platform/integrations/marketplace/registration).
## Get in touch
To list a curated application, enable a capability for a provider, or discuss an integration, contact us at .
---
> Source: https://technology.road.io/docs/platform/integrations/marketplace/registration
# Registering an application
> **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).
Before any OAuth flow can start, your application needs a client registered against the provider you want to operate with. A client is an OAuth `client_id` (plus a `client_secret` for confidential clients) and a set of registered redirect URIs, scoped to one provider.
There are three ways to obtain a client. Which ones are available depends on the provider's configuration (each is enabled independently). Whichever path you take, everything after registration is identical.
## The three registration paths
| Path | Who creates it | Best for |
| --------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| Curated template | Road staff create the catalogue entry; a provider administrator installs it | An application you want offered as a vetted, consistent option across providers |
| Provider installation | A provider administrator defines and registers it | An application bespoke to a single provider |
| Dynamic registration | Your application self-registers (RFC 7591) | Self-service onboarding where the provider has enabled it |
## Curated templates (Road-vetted)
If you want your application to appear as a pre-defined option in the Road catalogue, visible to providers as a one-click target with consistent branding, Road staff create a template for it. When a provider administrator installs from the template, the descriptive fields (logo, homepage URL, privacy policy URL) come from the template and are locked. The provider can override the customer-facing name and description, choose redirect URIs, and pick scopes within the template's maximum set.
> **Want your application in the curated catalogue?**
>
> Contact us at with the metadata you would like to register (name, description, logo, homepage URL, privacy policy URL, and the scopes you intend to request). Once vetted, Road creates the template and provider administrators can install it.
## Provider installations (provider-defined)
A provider administrator can register an application of their own from the Road dashboard. They own all of the metadata (name, description, logo, homepage URL, privacy policy URL), choose the redirect URIs, and pick which scopes the application may request. The provider then shares the generated `client_id` and one-time `client_secret` with you, securely and out of band.
This is the right path when you are an internal team or a partner of a single provider and the application does not need to appear as a vetted catalogue entry elsewhere.
## Dynamic client registration (RFC 7591)
Where a provider has enabled it, an application can register itself with no administrator involvement, using standard [RFC 7591](https://www.rfc-editor.org/rfc/rfc7591) Dynamic Client Registration.
### The registration endpoint
```http
POST https://api.road.io/1/oauth/{{providerSlug}}/register
Content-Type: application/json
{
"client_name": "My Application",
"redirect_uris": ["https://app.example/callback"],
"token_endpoint_auth_method": "none",
"scope": "openid offline_access sessions:read chargers:read"
}
```
A successful call returns `201` with the `client_id`, a `registration_access_token`, and a `registration_client_uri`. The registration endpoint only appears in the provider's discovery document when dynamic registration is enabled; when it is off the endpoint returns `404`.
### Managing a registration (RFC 7592)
The `registration_access_token` lets the application read, update, or delete its own registration using standard [RFC 7592](https://www.rfc-editor.org/rfc/rfc7592):
```http
GET https://api.road.io/1/oauth/{{providerSlug}}/register/{client_id}
PUT https://api.road.io/1/oauth/{{providerSlug}}/register/{client_id}
DELETE https://api.road.io/1/oauth/{{providerSlug}}/register/{client_id}
Authorization: Bearer {registration_access_token}
```
A `PUT` fully replaces the metadata and rotates the `registration_access_token`; store the new one and discard the old. A `DELETE` deregisters the client and revokes the grants issued to it.
### Public clients only (today)
Dynamic registration currently supports public clients only: `token_endpoint_auth_method` must be `none`. There is no client secret, so the application is authenticated at the token endpoint by PKCE alone. See [Authenticating your application](/docs/platform/integrations/marketplace/authentication) for what that means in practice.
### Gated scopes
Most scopes are open: any application can self-register for them where dynamic registration is enabled. A small number of higher-value scopes are **gated**: they cannot be requested via dynamic registration, and a `register` call that asks for one is rejected with `400 invalid_client_metadata`. To use a gated scope your application must be registered as a **curated template** or a **provider installation**, both of which are vetted by a person before the scope is available.
Gated today:
| Scope | Available through |
| ----- | ----------------------------------------------------------------------------------------------------------------------------- |
| `ere` | Curated template or provider installation only. See [ERE integration](/docs/platform/charge-point-operation/ere-integration). |
This keeps the platform open by default while letting Road reserve specialised data surfaces for approved integrations. Expect the gated set to grow as new specialised scopes land. To request access to a gated scope, contact us at .
## What you receive
After registration you will have:
1. A **`client_id`**.
2. A **`client_secret`**, for confidential clients only (provider installations and templates). Dynamic (public) clients have none.
3. The **discovery URL**, `https://api.road.io/1/oauth/{{providerSlug}}/.well-known/openid-configuration`. Fetch it once to get the rest of the OIDC endpoints.
There is no global client. Every provider you operate with gives you a distinct credential pair and issuer URL; the same application code is reused across providers.
## After registration, the flow is identical
Regardless of which path created the client, the authorisation, consent, token, and data-access steps are exactly the same. Continue with [Authenticating your application](/docs/platform/integrations/marketplace/authentication).
## Get in touch
To list a curated template, or to ask a provider to enable a registration path, contact us at .
---
> Source: https://technology.road.io/docs/platform/integrations/marketplace/authentication
# Authenticating your application
> **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).
Authentication is standard OpenID Connect: Authorisation Code flow with PKCE (S256). There is nothing Road-specific to implement, so you can use an off-the-shelf OAuth/OIDC client library. The authorize step happens on the provider dashboard (so the customer consents in their familiar branded environment); the token, userinfo and JWKS endpoints live on the Road API.
## Public vs confidential clients
- **Confidential clients** hold a `client_secret` (provider installations and templates). The secret is presented at the token endpoint. Keep it on a server you control; never ship it to a browser or mobile binary.
- **Public clients** have no secret (dynamic registrations, and any client that can not keep one). Single-page apps, browser extensions, mobile and desktop apps are all public clients. Do not embed or display a secret in the browser.
If your application runs in the browser, it is a public client. Register it as one and rely on PKCE.
## PKCE is required for all clients
Per OAuth 2.1, PKCE with `S256` is mandatory for every client, public and confidential alike. On each authorisation you generate a random `code_verifier`, send its `S256` challenge on the authorize request, and present the verifier on the token exchange. A missing or mismatched verifier fails the exchange.
## Discovery
```http
GET https://api.road.io/1/oauth/{{providerSlug}}/.well-known/openid-configuration
```
Returns a standard discovery document. Key fields:
- `authorization_endpoint` points at the provider dashboard. Open it in the user's browser; do not call it from a back channel.
- `token_endpoint`, `userinfo_endpoint`, `jwks_uri` live on the Road API.
- `registration_endpoint` is present only when dynamic registration is enabled for the provider.
- `scopes_supported` lists the scopes Road recognises.
- `response_types_supported: ["code"]`, `grant_types_supported: ["authorization_code", "refresh_token"]`, `code_challenge_methods_supported: ["S256"]`, `token_endpoint_auth_methods_supported: ["client_secret_post", "none"]`.
ID tokens are signed RS256; verify them against `jwks_uri`.
## The flow
```mermaid
sequenceDiagram
actor User
participant App as Your App
participant Browser
participant Dashboard as Provider Dashboard
participant API as Road API
User->>App: Click "Connect Road"
App->>App: Generate PKCE verifier + S256 challenge
App->>Browser: Redirect to authorization_endpoint
Browser->>Dashboard: Authorize (provider-branded UI)
Dashboard->>API: GET /1/oauth/:slug/authorize/details
API-->>Dashboard: { client, scopes, autoApprove, requiresResourceShare, ... }
Dashboard-->>User: Show scopes (and a data-sharing step if required)
User->>Dashboard: Approve
Dashboard->>API: POST /1/oauth/:slug/authorize/consent
API-->>Dashboard: redirect_uri?code=...&state=...
Dashboard->>Browser: Redirect
Browser->>App: Callback with code + state
App->>API: POST /1/oauth/:slug/token (code + verifier [+ secret])
API-->>App: { access_token, id_token, refresh_token }
```
Consent grants scopes only. For applications that request a data scope, the customer also chooses which resources to share; that step is handled separately from the protocol. See [Consent, grants and data sharing](/docs/platform/integrations/marketplace/consent-and-sharing).
## Authorisation request
The application redirects the user to:
```
{authorization_endpoint}?
client_id={client_id}&
redirect_uri={your_callback}&
response_type=code&
scope=openid+offline_access+sessions:read+chargers:read&
state={csrf_token}&
code_challenge={base64url_sha256(verifier)}&
code_challenge_method=S256&
nonce={random}
```
The `redirect_uri` must be registered on the client; unregistered URIs are rejected.
### Forcing or skipping the consent UI (`prompt`)
The standard OIDC `prompt` parameter is honoured:
| `prompt` | Behaviour |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| (omitted) | Show consent only if needed. If an active grant already covers the requested scopes, auto-approve and redirect immediately. |
| `consent` | Always show consent; never auto-approve. Useful to let the user adjust their data sharing. |
| `none` | Never show UI. Either a code is returned, or a standard error redirect: `?error=login_required`, `consent_required`, or `interaction_required`. Use in a hidden iframe for silent refresh. |
## Token exchange
```http
POST {token_endpoint}
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code={code}&
client_id={client_id}&
client_secret={client_secret}&
redirect_uri={your_callback}&
code_verifier={pkce_verifier}
```
Confidential clients include `client_secret`. Public clients omit it: PKCE alone authenticates the exchange. The response is the standard OIDC token envelope:
```json
{
"access_token": "...",
"token_type": "Bearer",
"expires_in": 3600,
"id_token": "...",
"refresh_token": "..."
}
```
For refreshing tokens and calling the API, see [Using the API](/docs/platform/integrations/marketplace/using-the-api).
## Grants are scoped to the user
A grant authorises one application for one user. It records the scopes that user approved. Different users of the same application each have their own grant, and each token acts only for the user it was issued to. Revoking a grant disconnects the application for that user only.
## Verifying the ID token
Verify `iss`, `aud` (your `client_id`), `exp`, and the signature against the JWKS at `{issuer}/.well-known/jwks.json`. The `kid` header indicates which JWKS entry to use. Pass a CSRF-resistant `state` on the authorize request and check it on the callback; Road round-trips `state` faithfully on success and error redirects.
---
> Source: https://technology.road.io/docs/platform/integrations/marketplace/consent-and-sharing
# Consent, grants and data sharing
> **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).
OAuth approves *scopes*: what kinds of thing an application may do. For applications that read charging data, the customer separately chooses *which resources* the application may see. The two are deliberately decoupled, so a customer can change what they share without re-running OAuth.
## What the user consents to
On the consent screen the customer sees the application and the scopes it has requested. The scopes Road supports are deliberately small and coarse:
| Scope | What it grants |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `openid` | OIDC marker; an ID token is issued and the basic account identifier (subject, account, provider) is available. |
| `offline_access` | A refresh token, so the application can act while the user is offline. |
| `profile` | The user's name and profile attributes. |
| `email` | The user's email address. |
| `account:read` | Read the user's own account details (name and contact). |
| `account:billing:read` | Read the account's billing details (`billing`, `creditBilling`). Also requires the user's billing permission, so an application cannot read billing just because the user is an account admin. |
| `ere` | Read the user's ERE charging data (sessions and charging-station metadata) through the [ERE API](/docs/platform/charge-point-operation/ere-integration). Subject to data sharing (below). A [gated scope](/docs/platform/integrations/marketplace/registration): available to curated templates and provider installations only. |
| `chargers:read` | Read the user's charging-station metadata. Subject to data sharing (below). |
| `sessions:read` | Read the user's charging sessions (CDRs). Subject to data sharing (below). |
The consent screen groups these for clarity (account information, charging data, ongoing access). `openid` is the OIDC marker scope: it grants the basic account identifier, not identity verification. The user approves a subset and a grant is recorded for them.
## Grants are per user
Each grant authorises one application for one user, recording the scopes that user approved. It is the runtime authority for that user's tokens. Revoking it disconnects the application for that user only; other users keep their own grants.
## Managing connected applications
A customer manages their own connections at **Settings → Personal → Connected apps**. From there they can:
- See which applications are connected and what each can access.
- Revoke an application, which immediately stops its access for that user.
- Adjust what a charging-data application can see (the data-sharing selection, below).
## Narrowing access after the grant
Access can be reduced after consent without the user re-authorising:
- A **provider administrator** can edit the application's scopes (or revoke the installation) from **My Locations → Integrations → Apps**. Tightening the installation reduces what every connected user's tokens can do; tokens still claiming a removed scope are rejected until refreshed, and the refreshed token no longer carries it.
- A **user** can revoke their grant or narrow their data-sharing selection at any time.
In all cases the change takes effect on the next request; existing tokens do not need to expire first. How an application observes these changes is covered in [Using the API](/docs/platform/integrations/marketplace/using-the-api).
## Choosing which data to share
Scopes say an application may read charging data; they do not say *whose* charging stations and sessions. For the charging-data scopes (`ere`, `chargers:read`, `sessions:read`) the customer makes a separate, explicit choice of which resources to share.
### When the data-sharing step appears
The data-sharing step is shown only when the requested scopes unlock a shareable resource family. Today that is the charging-data scopes (`ere`, `chargers:read`, `sessions:read`). Applications that request only account scopes (`openid`, `email`, `profile`, `account:read`, `account:billing:read`, `offline_access`) never see a sharing step; scope consent alone is enough.
### What a user can share
The customer shares at the level of **locations** and/or individual **EVSEs**. They can either:
- share everything they are currently eligible for, or
- select specific locations and/or EVSEs.
A location selection expands to the EVSEs under it.
### Eligibility
What a customer may share follows the same access rules as their "My Locations" view:
- A regular user can share the locations and EVSEs they own.
- An account administrator can share any location or EVSE in the account.
A customer can only ever select resources they currently have access to. Any read-time ownership rules specific to a particular data surface (for example ERE, which only ever returns a user's own charging stations) are applied by that surface on top of the share; see [ERE integration](/docs/platform/charge-point-operation/ere-integration).
## Adjusting sharing at any time
The data-sharing selection is stored independently of the OAuth grant, keyed to the installed application. The customer can change or revoke it at any time from **Settings → Personal → Connected apps** without re-running OAuth. If an application holds a data scope but the customer has shared nothing, the scoped data endpoints simply return an empty list; the grant stays valid.
For how an application reads that data, see [Using the API](/docs/platform/integrations/marketplace/using-the-api) and [ERE integration](/docs/platform/charge-point-operation/ere-integration).
---
> Source: https://technology.road.io/docs/platform/integrations/marketplace/using-the-api
# Using the API
> **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).
Once your application has tokens (see [Authenticating your application](/docs/platform/integrations/marketplace/authentication)), it calls the Road API on the user's behalf with the access token.
## Calling the API
Send the access token as a Bearer token:
```http
GET https://api.road.io/1/...
Authorization: Bearer {access_token}
```
Each call acts as the user the token was issued for, and returns only what that user's scopes and data sharing allow.
## Available data
The scoped data available today is the ERE charging data: the user's charging sessions and charging-station metadata. These are covered in full, including pagination and delta sync, on the [ERE integration](/docs/platform/charge-point-operation/ere-integration) page.
Account and identity data is available from the userinfo endpoint (below). More scoped APIs are being added over time.
## Token lifetime and expiry
Access tokens are short lived (`expires_in` seconds, typically one hour). Every rejected token carries a standard `WWW-Authenticate` challenge (RFC 6750) alongside the status code, so you can tell the scenarios apart without parsing error bodies:
| Response | Challenge | Meaning |
| -------- | ---------------------------- | ------------------------------------------------------------------------------------------------------ |
| `401` | `error="invalid_token"` | The token is expired, revoked, or no longer matches what the user has granted. Refresh and retry. |
| `403` | `error="insufficient_scope"` | The token does not hold the scopes this endpoint requires. Re-authorise requesting the missing scopes. |
A `401` on its own never means the user has disconnected your application: an expired token and a revoked one deliberately look identical at the resource. The refresh outcome tells you which it was.
## Detecting revocation
A user can revoke your application at any time from **Settings → Personal → Connected apps**, and a provider administrator can remove or narrow the installation. Both take effect on the next API call, not at token expiry. What your application sees:
1. A scoped call fails with `401` and `error="invalid_token"`:
```http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="road-api", error="invalid_token", error_description="application installation or grant is no longer active"
```
2. Attempt a refresh, exactly as for normal expiry. The outcome disambiguates:
| Refresh outcome | Meaning | What to do |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| Succeeds | The token had merely expired, or the granted scopes changed (the new token carries the currently granted scopes). | Retry with the new access token. |
| `400` with `invalid_grant` | The user revoked your application. | Stop polling, discard the stored tokens, and offer the user a way to reconnect. |
| `401` with `invalid_client` | The provider administrator removed the installation. | Same handling; reconnecting requires the installation to be restored first. |
If a freshly refreshed token then fails with `403` and `error="insufficient_scope"`, the granted scopes were narrowed below what the endpoint needs (for example, a provider administrator removed a data scope from the installation). Re-run the authorisation flow requesting the scopes; the user sees a consent screen for them.
A public (browser) client without a refresh token uses the silent `prompt=none` flow instead; an error redirect (`login_required` or `consent_required`) is the equivalent disconnection signal.
Distinguish all of the above from an empty `200`: that means the grant is intact and the user has simply shared no data (see [ERE integration](/docs/platform/charge-point-operation/ere-integration)). Treat `401` plus a failed refresh as "disconnected", never as an empty share.
## Refreshing tokens
If the user granted `offline_access`, the token response includes a refresh token. Exchange it at the token endpoint for a new access token:
```http
POST {token_endpoint}
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&
refresh_token={refresh_token}&
client_id={client_id}&
client_secret={client_secret}
```
Confidential clients include `client_secret`; public clients omit it.
> **Refresh tokens rotate**
>
> Every successful refresh issues a new refresh token and invalidates the previous one. Always store the new `refresh_token` from the response and discard the old one.
### Silent refresh in the browser
A public (browser) client with no refresh token can obtain a fresh code without showing UI by re-running the authorisation request with `prompt=none` in a hidden iframe. If the user can be auto-approved a code is returned; otherwise you get a standard error redirect (`login_required`, `consent_required`, or `interaction_required`) and should fall back to an interactive flow.
## UserInfo
```http
GET {userinfo_endpoint}
Authorization: Bearer {access_token}
```
Returns scope-filtered claims. `sub`, `provider_id`, and `account_id` are always present. The `email` scope adds `email` and `email_verified`; the `profile` scope adds `name`, `given_name`, `family_name`, `locale`, and `zoneinfo`. The same identity claims are embedded in the ID token when those scopes are granted.
## Common error cases
| Scenario | Symptom |
| ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| Unregistered `redirect_uri` | `400` from `authorize/details` with `invalid_request`. |
| Requested scope exceeds what the client may request | `400` with `invalid_scope`. |
| Bad PKCE verifier | `400` from `/token` with `invalid_grant`. |
| Replayed authorisation code | `400` from `/token`; codes are single use and short lived. |
| `prompt=none` but interaction needed | Redirect with `?error=login_required`, `consent_required`, or `interaction_required`. |
| User denies consent | Redirect with `?error=access_denied`. |
| Expired or revoked access token | `401` with `WWW-Authenticate: Bearer error="invalid_token"`; refresh to disambiguate (above). |
| User revoked the application | `401` with `error="invalid_token"` on calls; the refresh then fails with `400` `invalid_grant`. |
| Token lacks a scope the endpoint requires | `403` with `WWW-Authenticate: Bearer error="insufficient_scope"`. |
| Application holds a data scope but the user shared nothing | Scoped data endpoints return an empty list. Not an error. |
| Application token bound to a user with no account | `403` from the scoped data endpoints. |
## Operational notes
- **Secrets**: a confidential client's secret is shown once at registration and again only on rotation. Never expose it on a frontend.
- **Refresh token rotation**: refresh tokens are single use; record the new one on every refresh.
- **ID token verification**: verify `iss`, `aud`, `exp`, and the signature against the JWKS.
- **State**: pass a CSRF-resistant `state` on the authorisation request and verify it on the callback.
---
> Source: https://technology.road.io/docs/platform/integrations/csme
# Charging Station Message Exchange (CSME)
CSME exchanges information between a charging station connected to the Road CPO and an external party, such as a smart-charging service provider. It supports smart charging, V1G and V2G controls, and other use cases that depend on a station's real-time data.
CSME forwards a station's messages from the Road CPO to the external party as they happen: the meter values recorded during a charging session, and events such as changes in charging state. A third party can use this data to work out a charging schedule and issue commands back to control the charge. A fleet operator can use the same telemetry to understand charging patterns and make better use of the capacity it has.
## Terminology
Some common and new terms used throughout the document:
- *CSO / CPO*, Charging Station Operator, e.g. Road
- *CSMS*, Charging Station Management System, cloud-based backend maintained by Road where charging stations connect to
- *CSMEC or CSME Client*, Charging Station Message Exchange Client, an external party that is interested in receiving information from a charging station
- *CSMES or CSME Service*, Charging Station Message Exchange Service, the CPO service which enables REST communication between CPO and CSMEC
- *Pairing*, a process that establishes a link between a charging station and CSMEC that enables message exchange
- *Unpairing*, a process that disables the link between charging station and CSMEC
- Charging station *telemetry*, OCPP messages that are generated by a charging station and forwarded to CSMEC. In principle, any OCPP message type could be included but in practice the set of message types is limited to the ones that give insight into charging process such as: `StartTransaction`, `StopTransaction`, `MeterValues` for OCPP 1.6, `TransactionEvent` for OCPP 2.0.1, etc.
- Charging station *commands*, messages that CSMEC sends to a charging station via CSMES in order to influence the charging process or to inspect it, e.g. `SetChargingProfile`, `ClearChargingProfile`, `GetConfiguration`, `GetBaseReport`, etc.
## API overview
The CSME API (CSMES) is the CPO-side service that connects a charging station to a CSMEC. It forwards station data to the CSMEC and takes commands back from the CSMEC to send on to the station.
To establish a pairing and receive a station's messages, the CSMEC must implement and expose a number of API endpoints on its side.
The API follows RESTful principles and exchanges messages in JSON format.
### API versioning
Every API endpoint path has a major version prefix, e.g. `/1` in `/1/pairings`. This way we can guarantee full backwards compatibility within the scope of any major version. Within a major version the API will evolve and new payload fields or new API endpoints can and will be introduced, so clients need to make sure to parse messages accordingly and not fail on unknown fields. As soon as it's no longer possible to evolve the API without dropping or renaming existing fields a new major version of the API will be introduced.
### Schema validation
The API implements strict schema validation for incoming messages based on the respective OCPP version specification, ensuring that the messages are correctly formatted.
### Error handling
The API provides error codes and messages for various scenarios, such as `400` for `Bad Request` and `401` for `Unauthorized`, helping clients understand and rectify issues. A retry with exponential backoff is recommended for any connection error or `5xx` error code.
Application level errors for charging station commands and telemetry are returned in the response envelope. It is up to the CSMES and CSMEC to decide whether erroneous events should be re-submitted or whether they should be treated as a fire and forget.
### General flow
Following is a sample flow of events when a charging station pairing is initiated from CSMEC side, followed by charging station message exchange and eventual unpairing:
```mermaid
sequenceDiagram
actor User
participant CSMES as CSME Service
participant CSMEC as CSME Client
User->>+CSMEC: Generate pairing code
CSMEC-->>-User: Pairing code
User->>+CSMES: Pair a charging station
CSMES->>+CSMEC: Confirm pairing (pairing code
and charging station details)
CSMEC-->>-CSMES: Pairing confirmed
CSMES-->>-User: Pairing confirmed
loop During charging process
CSMES->>+CSMEC: Forward telemetry for
any paired charging station
CSMEC-->>-CSMES: Confirm reception
Note over CSMES,CSMEC: Parties independently exchange
telemetry messages and commands
CSMEC->>+CSMES: Send charging station commands
CSMES-->>-CSMEC: Confirm delivery
end
User->>+CSMES: Unpair charging station
CSMES->>+CSMEC: Propagate unpairing request
CSMEC-->>-CSMES: Unpairing confirmed
CSMES-->-User: Unpairing confirmed
```
### Pairing initiated by CSMEC
In this case the user enters a *pairing code* provided by the CSMEC into the CSMES system in order to establish a charging station pairing to CSMEC.
```mermaid
sequenceDiagram
actor User
participant CSMES as CSME Service
participant CSMEC as CSME Client
User->>+CSMEC: Generate pairing code
CSMEC-->>-User: Pairing code
User->>+CSMES: Pair a charging station
CSMES->>+CSMEC: Confirm pairing
POST /1/csmec/pairings
(charging station details)
CSMEC-->>-CSMES: Pairing confirmed
CSMES-->>-User: Pairing confirmed
```
The CSMES will initiate a request to CSMEC as follows:
#### `POST /1/csmec/pairings` (CSMEC API)
Sample request body:
```json
{
"id": "045a6ffd-b16e-48fa-ab67-86ca4ccf8ffe",
"pairingCode": "4A6BH28",
"chargingStation": {
"evses": [
{
"evseId": 1,
"capabilities": ["CHARGING_PROFILE_CAPABLE"],
"connectors": [
{
"id": 1,
"powerType": "AC_3_PHASE",
"maxVoltage": 230,
"maxAmperage": 16
}
]
}
]
}
}
```
`Pairing` object:
| Name | Type | Description | Required? |
| ----------------- | ----------------- | --------------------------------------------------------------------------- | --------- |
| `id` | `UUID` | Pairing ID, generated by CSMES when charging station pairing is established | Yes |
| `pairingCode` | `string` | Pairing code provided by the user | Yes |
| `chargingStation` | `ChargingStation` | Charging station details | Yes |
`ChargingStation` object:
| Name | Type | Description | Required? |
| ------- | -------- | ------------------------------ | --------- |
| `evses` | `EVSE[]` | List of charging station EVSEs | Yes |
`EVSE` object:
| Name | Type | Description | Required? |
| -------------- | ------------- | ------------------------------------------------------------- | --------- |
| `evseId` | `int` | EVSE identifier within a charging station: `1`, `2` and so on | Yes |
| `capabilities` | `string[]` | List of capabilities this EVSE supports | Yes |
| `connectors` | `Connector[]` | List of EVSE connectors | Yes |
`Connector` object:
| Name | Type | Description | Required? |
| ------------- | -------- | ----------------------------------------------------------------------------- | --------- |
| `id` | `int` | Connector identifier within an EVSE: `1`, `2` and so on | Yes |
| `powerType` | `string` | Connector power type, one of: `UNSPECIFIED`, `AC_1_PHASE`, `AC_3_PHASE`, `DC` | Yes |
| `maxVoltage` | `int` | Max voltage | Yes |
| `maxAmperage` | `int` | Max amperage | Yes |
Expected HTTP response codes:
| Code | Description |
| ----- | ---------------------------------------------------------------------------------- |
| `201` | Pairing successfully created |
| `401` | Request is unauthorized, ensure that valid authentication credentials are provided |
| `404` | Invalid or expired pairing code |
| `409` | This pairing has already been created |
| `5xx` | Internal server error occurred, client is advised to retry the request later |
### Pairing initiated by CSMES
In this case the user enters a *pairing code* provided by the CSMES into the CSMEC system in order to establish a charging station pairing to CSMEC.
```mermaid
sequenceDiagram
actor User
participant CSMES as CSME Service
participant CSMEC as CSME Client
User->>+CSMES: Generate pairing code
CSMES-->>-User: Pairing code
User->>+CSMEC: Pair a charging station
CSMEC->>+CSMES: Confirm pairing
POST /1/pairings
CSMES-->>-CSMEC: Pairing confirmed
(charging station details)
CSMEC-->>-User: Pairing confirmed
```
The CSMEC will initiate a request to the CSMES as follows:
#### `POST /1/pairings` (CSMES API)
Sample request body:
```json
{
"pairingCode": "4A6BH28"
}
```
Sample response body:
```json
{
"id": "045a6ffd-b16e-48fa-ab67-86ca4ccf8ffe",
"pairingCode": "4A6BH28",
"chargingStation": {
"evses": [
{
"evseId": 1,
"capabilities": ["CHARGING_PROFILE_CAPABLE"],
"connectors": [
{
"id": 1,
"powerType": "AC_3_PHASE",
"maxVoltage": 230,
"maxAmperage": 16
}
]
}
]
}
}
```
Refer to previous section for field descriptions.
Expected HTTP response codes:
| Code | Description |
| ----- | ---------------------------------------------------------------------------------- |
| `201` | Pairing successfully created |
| `401` | Request is unauthorized, ensure that valid authentication credentials are provided |
| `404` | Invalid or expired pairing code |
| `409` | This pairing has already been created |
| `5xx` | Internal server error occurred, client is advised to retry the request later |
### Unpairing initiated by CSMEC
#### `DELETE /1/pairings/:pairingId` (CSMES API)
Expected HTTP response codes:
| Code | Description |
| ----- | ---------------------------------------------------------------------------------- |
| `204` | Pairing successfully deleted |
| `401` | Request is unauthorized, ensure that valid authentication credentials are provided |
| `404` | Pairing not found |
| `5xx` | Internal server error occurred, client is advised to retry the request later |
### Unpairing initiated by CSMES
#### `DELETE /1/csmec/pairings/:pairingId` (CSMEC API)
Expected HTTP response codes:
| Code | Description |
| ----- | ---------------------------------------------------------------------------------- |
| `204` | Pairing successfully deleted |
| `401` | Request is unauthorized, ensure that valid authentication credentials are provided |
| `404` | Pairing not found |
| `5xx` | Internal server error occurred, client is advised to retry the request later |
### Sending telemetry to CSMEC
Once pairing is completed, the CSMES will forward relevant telemetry information to the CSMEC.
```mermaid
sequenceDiagram
participant CS as Charging Station
participant CSMES as CSME Service
participant CSMEC as CSME Client
CS->>CSMES: OCPP Commands
CSMES->>+CSMEC: Send telemetry for relevant commands
POST /1/csmec/messages
CSMEC-->>-CSMES: A 200 response
```
The CSMES will send the telemetry as follows:
#### `POST /1/csmec/messages` (CSMEC API)
Due to potentially high volume of messages from charging stations they will be aggregated and sent to CSMEC in batches, thus each request body will contain one or more messages.
Please note that frequency and contents of some messages that are generated by a charging station are subject to the charging station configuration.
Sample request body:
```json
{
"messages": [
{
"pairingId": "045a6ffd-b16e-48fa-ab67-86ca4ccf8ffe",
"protocol": "ocpp2.0.1",
"type": "TransactionEvent",
"payload": {
// JSON-formatted object specific to protocol and type
// In this example, `TransactionEvent`
"eventType": "Ended",
"evse": {
"connectorId": 1,
"id": 1
},
"meterValue": [
{
"sampledValue": [
{
"context": "Transaction.Begin",
"unitOfMeasure": {
"unit": "kWh"
},
"value": 88.643
}
],
"timestamp": "2024-01-26T16:40:38Z"
},
{
"sampledValue": [
{
"context": "Transaction.End",
"unitOfMeasure": {
"unit": "kWh"
},
"value": 88.663
}
],
"timestamp": "2024-01-26T16:41:26Z"
}
],
"seqNo": 6,
"timestamp": "2024-01-26T16:41:26Z",
"transactionInfo": {
"chargingState": "Charging",
"stoppedReason": "Remote",
"transactionId": "d4c36b70-31fa-44d2-87d3-a5105150d5cd"
},
"triggerReason": "EVCommunicationLost"
},
"errorPayload": null,
// Contains the complete response payload from CSMS for successful calls
"csmsResponsePayload": {
"idTokenInfo": {
"status": "Accepted"
}
},
// Contains error payload for failed calls (null when call succeeded)
"csmsErrorPayload": null
},
{
// Another message - example of a failed call
"pairingId": "045a6ffd-b16e-48fa-ab67-86ca4ccf8ffe",
"protocol": "ocpp2.0.1",
"type": "TransactionEvent",
"payload": {
"eventType": "Ended",
"seqNo": 7,
"timestamp": "2024-01-26T16:45:00Z",
"transactionInfo": {
"transactionId": "d4c36b70-31fa-44d2-87d3-a5105150d5cd"
},
"triggerReason": "StoppedByEV"
},
"errorPayload": null,
// Null when call failed
"csmsResponsePayload": null,
// Contains error details for failed calls
"csmsErrorPayload": {
"errorCode": "InternalError",
"errorDescription": "Database connection failed"
}
},
{
// Another message - example of a charging station's answer to a GetConfiguration command
"pairingId": "045a6ffd-b16e-48fa-ab67-86ca4ccf8ffe",
"protocol": "ocpp1.6",
"type": "GetConfiguration:Response",
"payload": {
"configurationKey": [
{ "key": "BoPenabled", "readonly": false, "value": "true" }
],
"unknownKey": []
},
// Set instead of `payload` if the charging station rejected the command
"errorPayload": null,
// Always null on a `:Response` message
"csmsResponsePayload": null,
"csmsErrorPayload": null
}
]
}
```
Sample response body. The response should contain information about the successful acceptance or rejection of each message:
```json
{
"messages": [
{ "success": true },
{
"success": false,
"error": { "code": "BAD_REQUEST", "message": "invalid protocol version" }
}
]
}
```
Message object:
| Name | Type | Description | Required? |
| --------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------- | --------- |
| `pairingId` | `UUID` | Pairing ID | Yes |
| `protocol` | `string` | Protocol type, one of: `ocpp1.5`, `ocpp1.6`, `ocpp2.0.1`, `ocpp2.1` | Yes |
| `type` | `string` | Message type, specific to the `protocol` used, e.g.: `MeterValues`, `TransactionEvent`, `GetConfiguration:Response`, etc. | Yes |
| `payload` | `object` | The actual message body that's coming from a charging station (null when the charging station rejected a command) | No |
| `errorPayload` | `object` | Error details when the charging station rejected a command (null otherwise) | No |
| `csmsResponsePayload` | `object` | The complete CSMS response payload for successful calls (null for failed calls) | No |
| `csmsErrorPayload` | `object` | Error details for failed calls (null for successful calls) | No |
Currently, CSME supports forwarding telemetry as raw OCPP messages. Learn more about the protocols on [OCA website](https://openchargealliance.org/protocols/open-charge-point-protocol/). Depending on value of `protocol` message `payload` will conform to either [OCPP 1.6 schemas](https://github.com/mobilityhouse/ocpp/tree/master/ocpp/v16/schemas) or [OCPP 2.0.1 schemas](https://github.com/mobilityhouse/ocpp/tree/master/ocpp/v201/schemas).
The `csmsResponsePayload` field contains the complete response that the CSMS (Charging Station Management System) sent back to the charging station for the corresponding request. For example, when a charging station sends a `TransactionEvent`, the `csmsResponsePayload` will contain the CSMS's response that may contain `idTokenInfo`, `transactionId` (for OCPP 1.6), or other relevant response data. This enables CSME clients to have full context of the charging station communication, including both the original request and the CSMS response.
A charging station's answer to a `GetConfiguration` command arrives as a message of its own, with a `type` of `GetConfiguration:Response`. Its `payload` is the answer. If the charging station rejected the command, `payload` is `null` and the error is in `errorPayload`. `csmsResponsePayload` and `csmsErrorPayload` are always `null` on this message. No other command's answer is forwarded.
The configuration a `GetBaseReport` command asks for arrives in `NotifyReport` messages, which carry the `requestId` the command was sent with.
Both are forwarded only when enabled for the CSMEC, as `GetConfiguration:Response` and `NotifyReport`. They go to every CSMEC paired to the charging station, not only the one that sent the command.
The `csmsErrorPayload` field contains error details including error code and description in case of an error during the handling of OCPP message by CSMS. In this case `csmsResponsePayload` will be `null`.
#### Error Payload Structure
The `csmsErrorPayload` and `errorPayload` object structure for failed calls typically includes:
```json
{
"errorCode": "string", // OCPP-defined error code (e.g., "InternalError", "NotSupported")
"errorDescription": "string" // Human-readable error description
}
```
Common error codes that may appear in `csmsErrorPayload` (for the complete list refer to the OCPP spec):
- `InternalError`: Server-side processing error
- `NotSupported`: The requested operation is not supported
- `SecurityError`: Security validation failed
- `FormationViolation`: Request format validation failed
- `PropertyConstraintViolation`: Property value constraint violated
- `OccurrenceConstraintViolation`: Required property missing or unexpected property present
Expected HTTP response codes:
| Code | Description |
| ----- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `200` | Request successfully processed. Note that due to batch nature of this endpoint individual messages could still fail, refer to the table below |
| `401` | Request is unauthorized, ensure that valid authentication credentials are provided |
| `5xx` | Internal server error occurred, client is advised to retry the request later |
Every message delivery status will contain an `error` object in case when there was a failure with its processing. The following table describes possible values of the error object's `code` attribute:
| Code | Description |
| ------------------ | --------------------------------------------------------- |
| `BAD_REQUEST` | Message payload or protocol is invalid |
| `ENTITY_NOT_FOUND` | Pairing not found |
| `ACCESS_DENIED` | Client has no access to requested pairing |
| `INTERNAL_ERROR` | Internal server error, client may retry the message later |
### Receiving commands from CSMEC
The CSMEC will send commands to CSMES in order to control charging process.
```mermaid
sequenceDiagram
participant CS as Charging Station
participant CSMES as CSME Service
participant CSMEC as CSME Client
CSMEC->>+CSMES: Send command to charging station
POST /1/messages
CSMES->>+CS: OCPP Command
CS-->>-CSMES: Confirm reception
CSMES-->>-CSMEC: A 200 response
```
#### `POST /1/messages` (CSMES API)
A charging station answers a command asynchronously, so the response to this call only confirms that the command was delivered.
Sample request body:
```json
{
"messages": [
{
"pairingId": "045a6ffd-b16e-48fa-ab67-86ca4ccf8ffe",
"protocol": "ocpp2.0.1",
"type": "SetChargingProfile",
"payload": {
// JSON-formatted object specific to protocol and type
// In this example, `SetChargingProfile`
"evseId": 1,
"chargingProfile": {
"id": 1,
"stackLevel": 1,
"chargingProfilePurpose": "TxProfile",
"chargingProfileKind": "Absolute",
"chargingSchedule": [
{
"id": 1,
"chargingRateUnit": "W",
"chargingSchedulePeriod": [{ "startPeriod": 0, "limit": 11000 }]
}
],
"validTo": "2024-01-16T15:09:15Z",
"transactionId": "4609d4ce-1c7c-4e31-be54-63c6d1541e6c"
}
}
}
]
}
```
Sample response body. The response will contain information about the successful acceptance or rejection of each message:
```json
{
"messages": [
{ "success": true },
{
"success": false,
"error": { "code": "BAD_REQUEST", "message": "invalid protocol version" }
}
]
}
```
Expected HTTP response codes:
| Code | Description |
| ----- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `200` | Request successfully processed. Note that due to batch nature of this endpoint individual messages could still fail, refer to the table below |
| `401` | Request is unauthorized, ensure that valid authentication credentials are provided |
| `5xx` | Internal server error occurred, client is advised to retry the request later |
Every message delivery status will contain an `error` object in case when there was a failure with its processing. The following table describes possible values of the error object's `code` attribute:
| Code | Description |
| --------------------- | ---------------------------------------------------------------------------------------------- |
| `BAD_REQUEST` | Message payload or protocol is invalid |
| `ENTITY_NOT_FOUND` | Pairing not found |
| `ACCESS_DENIED` | Client has no access to requested pairing |
| `SERVICE_UNAVAILABLE` | Charging station is unavailable (temporarily disconnected), client may retry the message later |
| `INTERNAL_ERROR` | Internal server error, client may retry the message later |
## Environments
Road exposes 2 CSME environments: staging and production. These are the base URLs of them:
- Staging: https://csme.public.road.dev
- Production: https://csme.road.io
## Authentication and authorisation
CSME exchanges data between two parties, so every request is authenticated on both sides. This section covers the authentication methods CSME supports on each side.
### CSME service
CSMEC can use pre-shared secret token to authenticate against CSMES. The value of secret token `CSMES_TOKEN` should be passed in `Authorization` header, e.g.:
```shell
curl -XPOST \
-H "Authorization: Bearer $CSMES_TOKEN" \
-H "Content-Type: application/json" \
-d '{"pairingCode": "4A6BH28"}' \
https://csme.road.io/1/pairings
```
### CSME client
CSME Service supports 2 authentication methods when calling CSME Client API endpoints.
#### OAuth2 client credentials flow
For this method CSMEC needs to provide `client ID`, `client secret` and token URL. Access token `CSMEC_TOKEN` acquired by CSMES will be passed in `Authorization` header with every request to CSMEC, e.g.:
```shell
curl -XDELETE \
-H "Authorization: Bearer $CSMEC_TOKEN" \
https://api.provider.com/1/csmec/pairings/045a6ffd-b16e-48fa-ab67-86ca4ccf8ffe
```
#### Pre-shared API key
Alternative authentication method is a pre-shared API key `CSMEC_API_KEY` that is passed in `x-api-key` header, e.g.:
```shell
curl -XDELETE \
-H "X-Api-Key: $CSMEC_API_KEY" \
https://api.provider.com/1/csmec/pairings/045a6ffd-b16e-48fa-ab67-86ca4ccf8ffe
```
---
> Source: https://technology.road.io/docs/roaming/concepts/overview
# Overview
The Road Roaming Hub is a standalone, multi-tenant OCPI hub. It connects Charge Point Operators (CPOs), eMobility Service Providers (eMSPs) and CPMS platforms to each other, so each one integrates once and reaches every network on the hub. It works alongside the Road Charge Point Management System (CPMS) but is a product in its own right: you connect over OCPI, whatever platform you run.
There are three ways to use it.
**Roaming-as-a-Service.** Connect and use Road's existing roaming agreements instead of building your own. A CPO reaches the drivers of every eMSP on the network; an eMSP reaches every location. This suits an operator that wants coverage without negotiating agreements one at a time. See [joining as a CPO](/docs/roaming/guides/joining-as-a-cpo) and [joining as an eMSP](/docs/roaming/guides/joining-as-an-emsp).
**Network extension.** A CPMS that already runs its own roaming connects to the hub as one more link and gains the locations and drivers in Road's network, through a single integration, on top of the agreements it already holds.
**Roaming infrastructure.** Connect over OCPI and run your roaming transactions on the hub while keeping full ownership of your own agreements. This is the basis of [Roaming PaaS](/docs/roaming/guides/roaming-paas), where you run your own roaming network on Road's platform.
---
> Source: https://technology.road.io/docs/roaming/concepts/coverage
# Coverage
The Road Roaming Hub connects to CPOs and eMSPs across Europe, both through direct integrations and through partnerships with established roaming hubs:
- **eClearing**, with strong coverage in the Netherlands.
- **Gireve**, with wide coverage across France.
Between the direct integrations and the hub partnerships, drivers reach a large European network. The map below shows the current coverage; to explore it in detail, see the [interactive map](https://mapsdk.road.io/).
---
> Source: https://technology.road.io/docs/roaming/guides/joining-as-a-cpo
# Joining the Road network as a CPO
A Charge Point Operator (CPO) connects to the Roaming Hub to make its locations reachable by eMSPs and their drivers. How you connect depends on your setup.
## Peer-to-peer with Road's eMSP
Connect your CPMS to the hub through a peer-to-peer agreement with Road's eMSP, using the standard OCPI MSP-CPO handshake (OCPI 2.1.1). Your locations join Road's eMSP network and become visible on Road's maps and apps, and Road's cardholders can charge at your stations. The agreement is between you and Road's eMSP, and transactions settle directly between the two of you.
To set up a peer-to-peer connection, contact the roaming team at .
## Hub connection
To reach every eMSP on the Road network rather than Road's eMSP alone, connect to the hub as an extension of your own network (OCPI 2.1.1). The hub takes your location data and broadcasts it to every eMSP on the network, keeping your own party ID so your branding and identity are preserved. Your locations become reachable to the cardholders of all those eMSPs, a much larger pool of drivers.
Here the agreement is between you and the hub. Transactions settle between you and Road, and Road in turn settles with each eMSP as if the charge came from Road. You reach Road's full set of eMSP agreements while keeping your own identity.
To set up a hub connection, contact the roaming team at .
## Roaming-as-a-Service (no party ID)
If you have no party ID or existing roaming agreements, Road can run roaming on your behalf. Both the peer-to-peer and hub modes are available; the difference is that Road broadcasts your locations and CDRs under its own party ID.
To set up Roaming-as-a-Service, contact the roaming team at .
---
> Source: https://technology.road.io/docs/roaming/guides/joining-as-an-emsp
# Joining the Road network as an eMSP
An eMobility Service Provider (eMSP) connects to Road through a peer-to-peer OCPI MSP-CPO agreement, giving its drivers access to the charging stations operated by CPOs on the Road network. The hub supports OCPI **2.1.1** and **2.2.1**.
Once connected, all of Road's CPO locations are reachable by the eMSP's cardholders. The agreement is between Road and the eMSP, and transactions settle directly between the two parties.
To set up a peer-to-peer eMSP connection, contact the roaming team at .
---
> Source: https://technology.road.io/docs/roaming/guides/roaming-paas
# Roaming as a Platform-as-a-Service (PaaS)
Beyond connecting to Road's own network, you can run your own roaming network on the same hub. With Roaming PaaS you get the hub technology that powers Road's network, but with full control over your own agreements and configuration: your own ecosystem of CPOs and eMSPs, on Road's infrastructure.
## How it works
- **Your agreements, your control.** You develop your own roaming agreements and configure them in the platform. The network is yours.
- **The same connection modes.** Peer-to-peer, hub connection and Roaming-as-a-Service are all available to your network, so you set up your own eMSP and CPO integrations the way Road does for its own.
- **OCPI infrastructure.** Road provides the compliant OCPI platform, so you build your network rather than the plumbing under it.
## Network management services
Running a roaming network is ongoing work. Road offers services you can fold into your operation:
- **Settlement.** Financial reconciliation between the CPOs and eMSPs on your network.
- **Dispute resolution.** Handling and resolving transaction disputes between participants.
- **Monitoring and reporting.** Session data, network usage and financial performance.
## Getting started
To build your own roaming network on Road, contact the roaming team at to talk through setup, configuration and integration.
---
> Source: https://technology.road.io/docs/charge-now/concepts/overview
# ChargeNow (Early Access)
## What is ChargeNow?
ChargeNow is a single API that gives your product access to EV charging infrastructure across multiple networks. Behind the scenes, charging stations speak different protocols, network operators each have their own quirks, and reliability levels vary considerably across the industry. ChargeNow handles all of that complexity so you don't have to.
If you're a company that wants to offer EV charging as part of your product (a fleet management platform, a mobility app, a parking service), ChargeNow is the interface between your product and the charging world.
## How it works
You communicate with ChargeNow through a simple REST API. ChargeNow translates your requests into the appropriate commands for the underlying charging infrastructure and returns a consistent, predictable response regardless of which network or station is involved.
Your data is isolated within an **integration**, a dedicated account that scopes your API tokens, your connected charge networks, and all session data. You can have multiple integrations if you need to separate environments or customer segments.
## Getting an integration
Integrations are not self-serve. Contact our sales team to discuss your use case and get set up. We'll create your integration, issue your first API token, and give you access to the sandbox environment for development and testing.
## Billing and your responsibilities
ChargeNow bills your integration for the cost of every charging session your users generate. The energy cost, session fees, and any applicable network charges are your responsibility.
**What your users owe you is entirely up to you.** ChargeNow has no involvement in how you price, invoice, or collect payment from your end users. Whether you pass costs through at cost, mark them up, bundle them into a subscription, or absorb them entirely, that is your product decision and your payment infrastructure to manage.
Every session includes a final `cost` and `kwh` value once it reaches the `Settled` state. These are the definitive figures to use when accounting for what a session cost. The `cost` value is **excluding VAT**; it is the raw, net price. Apply VAT yourself if your billing model requires it.
## Connecting to charge networks
Before you can start sessions on a particular charging network, that network must be connected to your integration. Network connections are configured by our team during onboarding. Contact us to add or change your connected networks.
## Try the demo
Before writing any code, you can explore the API through our interactive demo at [chargenow-demo.road.io](https://chargenow-demo.road.io). Enter your sandbox Bearer token, the API base URL, and optionally a Google Maps API key to browse real charge point locations and walk through a full session. All credentials are stored locally in your browser, we never store them.
## Building with AI
If you are using an AI assistant to help implement your integration, a machine-readable reference is available at `https://chargenow.road.io/llms.txt`. It covers all endpoints, request and response shapes, error reason codes, session states, and the typical integration flow in a single document, ready to paste into any AI tool.
---
> Source: https://technology.road.io/docs/charge-now/guides/authentication
# Authentication
## Getting credentials
Tokens are issued by the ChargeNow team. To get started, contact us with your organisation name and a brief description of what you're building. We'll create an integration and issue your first token.
For sandbox access, see the [Sandbox](/docs/charge-now/guides/sandbox) guide.
## Making authenticated requests
Include your token as a Bearer token in the `Authorization` header of every request. All requests without a valid token are rejected.
## Token lifecycle
Tokens have a long validity period and are also active until explicitly revoked. We'll tell you the exact expiry when we issue your token.
To rotate or revoke a token, contact us. We recommend rotating tokens periodically and immediately if you suspect a token has been compromised.
**Keep your token secret.** Do not commit it to source control or expose it in client-side code.
## Authentication errors
If a request fails due to authentication, you will receive a `401` response. The most common causes are a missing token, an expired token, or a token that has been revoked. See the [Errors](/docs/charge-now/guides/errors) guide for how to handle these.
---
> Source: https://technology.road.io/docs/charge-now/guides/sandbox
# Sandbox
## Requesting access
The sandbox requires separate credentials from production. Contact us to request sandbox access, we'll set up a sandbox integration and provide:
- A sandbox Bearer token
- A set of test charge point IDs
## Base URL
```
https://chargenow.road.io
```
The sandbox uses the same base URL as production. The only difference is your credentials: swap in your sandbox token and you're ready to go.
## What the sandbox simulates
The sandbox replicates the full charging session lifecycle:
**Pending** → **Starting** → **Started** → **Stopping** → **Stopped** → **Settled**
This includes realistic async behaviour: sessions take a few seconds to move from `Starting` to `Started`, mirroring the timing you'd see with real charge points. This lets you test your polling logic and state-change handling before going live.
Locations and charge points behave the same as production: you can query them via the Locations API and use `enrich_charge_points=true` as normal.
## Limitations
- **No real charging infrastructure** - the sandbox does not communicate with physical charge points
- **Billing data is not real** - cost and energy values are simulated
- **Sandbox data may be reset** - we may periodically reset sandbox data; don't rely on it for long-term storage
- **Test charge points only** - use the IDs we provide; charge points from production do not exist in the sandbox
## Testing state transitions
Each test charge point ID we provide is pre-configured to exercise a specific scenario. Currently, the sandbox supports:
- **Happy path** - session starts, progresses through all states, and settles successfully
More test scenarios will be added over time. If you need a specific edge case, contact us.
---
> Source: https://technology.road.io/docs/charge-now/guides/quality-metrics
# Quality Metrics
Quality data is computed from real session history, success rates, latency, silent sessions, and more, and distilled into a single **score** plus a human-readable **reliability** label.
Quality data is returned on each charge point when you fetch a location with `enrich_charge_points=true`. If a charge point has no recorded sessions yet, quality data is not available. The full `quality` object is in the [reference](/docs/charge-now/reference/chargenow-api/location).
## What quality data includes
| Field | What it is |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `score` | Composite quality score, `0` (worst) to `100` (best). |
| `reliability` | Human-readable label for the score: `excellent`, `good`, `fair`, `poor`, `very_poor`, or `insufficient_data`. |
| `total_sessions` | Number of sessions the metrics are computed from. |
| `success_rate` | Proportion of sessions that completed successfully. |
| `silent_session_rate` | Proportion where the charge point never confirmed it had started. |
| `avg_start_latency_sec` | How quickly the charge point confirms charging has started. |
| `avg_settlement_latency_sec` | How quickly it confirms the final cost after stopping. |
## Understanding the reliability label
The **reliability** label maps the numeric `score` to a tier that's easy to display in a UI or use in business logic. A minimum of **5 sessions** is required before a meaningful label is assigned.
| Label | Score range | What it means |
| ------------------- | ----------- | ------------------------------------------------------------------- |
| `excellent` | 80–100 | Consistently successful sessions with low latency |
| `good` | 60–79 | Reliable overall with occasional issues |
| `fair` | 40–59 | Noticeable problems, drivers may experience delays or failures |
| `poor` | 20–39 | Frequent issues, consider warning users before they start a session |
| `very_poor` | 0–19 | Serious reliability problems, sessions regularly fail or stall |
| `insufficient_data` | n/a | Fewer than 5 sessions recorded; not enough history to judge |
## Understanding individual metrics
### score
A composite quality score from **0** to **100** that summarises overall charge point health. It blends five weighted components:
| Component | Weight | What it measures |
| ----------------------------- | ------ | ---------------------------------------------------------------------------------------- |
| Session success rate | 35% | Proportion of sessions that reach `Settled` |
| Start failure rate (inverse) | 25% | Proportion of sessions that do *not* fail before reaching `Started` |
| Silent session rate (inverse) | 15% | Proportion of settled sessions where the charge point *did* communicate `Started` status |
| Start latency | 15% | How quickly the charge point responds after session creation |
| Settlement latency | 10% | How quickly the charge point settles after stopping |
When a component has no data (e.g. no sessions have reached `Started` yet), its weight is redistributed proportionally among the remaining components. Charge points with fewer than 5 sessions receive a neutral score of **50** and a reliability label of `insufficient_data`.
### success\_rate
The ratio of **settled** sessions to **total** terminal sessions (0.0–1.0). A settled session completed the full charging cycle and reached the `Settled` billing state.
| Value | Reading |
| ------------- | ---------------------------------------------------------------------- |
| `0.95`+ | Excellent; almost every session completes |
| `0.80`–`0.94` | Good; occasional failures |
| Below `0.80` | Investigate; the charge point may have hardware or connectivity issues |
### silent\_session\_rate
The ratio of settled sessions where the charge point **never communicated `Started` status** back to the system (0.0–1.0). A silent session does not mean the car wasn't charging: the charge point may well have been delivering energy but failed to report the `Started` state. The session still settles because the CPO confirms billing independently.
| Value | Reading |
| ------------- | ---------------------------------------------------------------- |
| Below `0.05` | Normal; most charge points have a few silent sessions |
| `0.05`–`0.15` | Elevated; likely communication or reporting delays |
| Above `0.15` | High; the charge point regularly fails to report charging status |
A high silent session rate degrades the driver experience: without a `Started` confirmation the app can't show live progress, even though the vehicle is likely charging, which causes confusion and support requests.
### avg\_start\_latency\_sec
Average seconds between session creation (`Pending`) and the charge point confirming energy delivery (`Started`). `null` when no session has ever reached `Started`.
| Value | Reading |
| --------- | ------------------------------------------------------- |
| Under 15s | Fast; typical for well-connected charge points |
| 15–45s | Normal |
| Over 45s | Slow; the charge point or CPO backend may be under load |
The charge point may already be delivering energy but be slow to report `Started`, so a driver is left waiting without confirmation in the app.
### avg\_settlement\_latency\_sec
Average seconds between the session stopping (`Stopped`) and final billing confirmation (`Settled`). `null` when there is no settlement-latency data. Values are typically in the hundreds to thousands of seconds because settlement depends on CPO billing systems.
| Value | Reading |
| ------------------------ | ------------------------------------------- |
| Under 300s (5 min) | Fast settlement |
| 300–3600s (5 min – 1 hr) | Normal |
| Over 3600s (1 hr+) | Slow; the CPO may batch-process settlements |
High settlement latency means drivers see a pending charge for longer, and you may need to hold a pre-authorisation amount for an extended period.
## Integration ideas
### Display a reliability badge
Map the `reliability` label to a colour and icon in your UI so drivers can judge charge point quality at a glance. For example, `excellent` green, `fair` yellow, `very_poor` red, and a neutral grey for `insufficient_data`.
### Adjust pre-authorisation amount
When `avg_settlement_latency_sec` is high, the final billing amount may arrive much later than expected. Consider increasing the pre-authorisation hold proportionally to cover delayed billing.
### Show driver warnings
When `reliability` is `poor` or `very_poor`, surface a warning so the driver can pick a better charge point. For a high `silent_session_rate`, tell drivers the charge point may not report live status: their vehicle is likely charging even if the app can't confirm it.
### Sort or filter charge points by quality
Use `score` to rank nearby charge points so the best options appear first, or filter out charge points below a minimum score to avoid showing unreliable options.
### Monitor fleet charge points
If you manage a fleet, poll quality metrics periodically and alert when a previously reliable charge point degrades, for example when `score` drops below a threshold or `success_rate` falls significantly.
### Handle insufficient data
When `reliability` is `insufficient_data`, the charge point has fewer than 5 recorded sessions. Show a neutral state (e.g. "New") rather than hiding the charge point or showing a negative indicator.
---
> Source: https://technology.road.io/docs/charge-now/guides/errors
# Errors
Every failure returns a standard HTTP status code and a consistent body, `application/problem+json` ([RFC 7807](https://www.rfc-editor.org/rfc/rfc7807)):
```json
{
"title": "Unauthorized",
"status": 401,
"detail": "Authentication required.",
"reason": "auth.missing_token",
"meta": {}
}
```
| Field | Description |
| -------- | ------------------------------------------------------------------------------------------------------------------------- |
| `title` | Short summary, matching the HTTP status phrase. |
| `status` | HTTP status code. |
| `detail` | Human-readable explanation, safe to show an end user. |
| `reason` | Stable machine-readable code. **Handle errors on this**, not on `title` or `detail`, which are for humans and may change. |
| `meta` | Extra context, such as which field failed validation. May be empty. |
## Status codes
| Status | When it occurs |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `400 Bad Request` | Invalid body or parameters. `meta` carries field-level detail (below). |
| `401 Unauthorized` | Authentication failed. See the `auth.*` `reason`. |
| `403 Forbidden` | The token lacks permission for this action. |
| `404 Not Found` | The resource does not exist. |
| `409 Conflict` | A conflict prevented the operation, e.g. an `Idempotency-Key` reused with a different body (`reason: idempotency.failure`). |
| `412 Precondition Failed` | A required condition was not met, e.g. the charge point is not `Available`. |
| `429 Too Many Requests` | Rate limit exceeded. Back off and retry. |
| `503 Service Unavailable` | Temporary; retry after a short delay. |
| `504 Gateway Timeout` | The request timed out; retry after a short delay. |
| `500 Internal Server Error` | Something went wrong on our side. |
## Retrying
`503` and `504` responses carry a `Retry-After` header (seconds); use exponential backoff for repeated failures. An operation sent with an `Idempotency-Key` is safe to retry on a network error without creating a duplicate. Reusing a key with a *different* body returns `409` with `reason: idempotency.failure`: use the original result, or a new key for a distinct operation.
## Validation detail
On a `400`, `meta` pinpoints what was wrong, so you can show a precise message or fix the request in code:
```json
"meta": {
"violations": [
{
"field": "chargePointId",
"message": "Provide chargePointId to start a session",
"validationRule": "mutually_exclusive"
}
]
}
```
## Authentication errors
See [Authentication](/docs/charge-now/guides/authentication) for the full list of `auth.*` reason codes.
---
> Source: https://technology.road.io/docs/charge-now/guides/sessions
# Sessions
A **session** tracks a single charging transaction at a charge point, from the moment you request it until the final cost is confirmed. Every charge a user initiates through ChargeNow creates a session, which progresses through a fixed sequence of states and eventually reaches `Settled` with confirmed energy and cost figures.
## The session lifecycle
A session moves through a fixed sequence of states. The happy path runs start, then stop, then settle; two states sit off it.
What each state means:
| State | What it means |
| --------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `Pending` | Session created, command being prepared |
| `Starting` | Start command sent to the charge point, waiting for confirmation |
| `Started` | Charge point confirmed, energy is being delivered |
| `StopRequested` | Stop command dispatched, waiting for charge point acknowledgement |
| `Stopping` | Charge point acknowledged the stop command |
| `Stopped` | Charging has ended, awaiting final billing data from the network |
| `Settled` | Final cost and energy confirmed, session complete |
| `Failed` | The session could not be started or was interrupted |
| `Abandoned` | All stop retries exhausted; charger state is unknown. Final billing data can still arrive from the network and settle it |
**Poll or subscribe to track state.** The start and stop calls dispatch a command and return immediately; the state advances only once the charge point confirms, usually within seconds to a minute. Poll the session or subscribe to [webhooks](/docs/charge-now/guides/webhooks) rather than assuming an immediate transition.
## Tracking changes in real time
Instead of polling, subscribe to the [`session.updated`](/docs/charge-now/guides/webhooks/session-updated) webhook. It fires on every state transition and whenever energy or cost figures are updated, including when a correction is applied after initial settlement.
---
> Source: https://technology.road.io/docs/charge-now/guides/sessions/start-session
# Start a session
## Before you start
- **Location** - a physical site with one or more charge points
- **Charge Point** - an individual charging unit, identified by its `id`. Sessions can only be started on charge points with `Available` status.
Use the map endpoint to find locations near a coordinate, then retrieve the charge points at the location you want.
For the full session state machine, see [Sessions](/docs/charge-now/guides/sessions).
## Pricing and tariffs
Tariff data is not always available. Whether a charge point exposes pricing depends entirely on the charging network: some publish structured tariffs, others provide nothing. ChargeNow surfaces whatever the network reports; it does not calculate or guarantee pricing upfront.
To fetch tariff information, include `?enrich_tariffs=true` when retrieving a location. Each charge point in the response may then include a `tariffs` array. When `tariffs` is empty or absent, no machine-readable pricing is available for that charge point.
**You can still start a session on a charge point with no tariff data.** The absence of pricing information does not prevent charging. The confirmed `cost` and `kwh` will be available once the session settles.
## Starting a session
Call the start endpoint with the charge point ID and optionally a `reference`. ChargeNow creates the session, dispatches a start command to the charge point, and returns immediately with the new session in `Pending` state.
The session then progresses asynchronously: `Pending → Starting → Started` as the charge point confirms. Poll the session endpoint or subscribe to the [`session.updated`](/docs/charge-now/guides/webhooks/session-updated) webhook to track the transition. A session that never reaches `Started` will move to `Failed`.
Sessions can only be started on charge points with `Available` status. Attempting to start on any other status returns `412`.
## Stopping a session
To stop a session, call the stop endpoint with the session ID. This dispatches a stop command to the charge point and the session moves to `StopRequested`, then `Stopping`, then `Stopped` as the charge point confirms.
**When a stop can be requested:**
A stop can only be requested when the session is in `Started`. You cannot stop a session in a transitional state:
| Current state | Stop request |
| ---------------------------------------------- | ------------------------------------------------------------------------- |
| `Started` | Accepted, session moves to `StopRequested` |
| `Pending` / `Starting` | Rejected `412`, session has not started yet |
| `StopRequested` / `Stopping` | Rejected `412` `session.stop_already_requested`, stop already in progress |
| `Stopped` / `Settled` / `Failed` / `Abandoned` | Rejected `412`, session already ended |
**What happens if the charge point rejects or does not respond:**
If the charge point rejects the stop command, the API returns `503` with `reason: session.charge_point_rejected`. The session remains in `Started` and you can retry.
If the charge point becomes unreachable after a stop is requested, the system retries automatically. If all retries are exhausted the session moves to `Abandoned`. Final billing data from the network can still arrive later and settle the session.
## Unlocking a stuck connector
If a driver's cable will not release (commonly reported right after a session ends, but this can also happen with no session involved at all), call the unlock endpoint with the charge point ID. This sends an unlock command directly to the charge point.
This is a charge-point action, not a session action: no session needs to exist, and if one does, unlocking is allowed regardless of its current state, including while it is still `Started`. The charge point must belong to one of your integration's connections.
The response reflects the charge point's immediate outcome rather than an async acknowledgement:
- `200` with `{"status": "accepted"}`, the charge point accepted the unlock command
- `404` `charge_point.not_found`, the charge point does not exist or does not belong to your integration
- `503` `unlock_charge_point.rejected`, the charge point rejected the unlock command or does not support it
## Using the reference field for cost apportionment
Every session you start can include a `reference`, a string you choose that links the session back to something in your own system: a user ID, a booking reference, an order number, or anything else that makes sense to you.
This is the primary mechanism for connecting a settled session back to the user who generated the cost. When a session reaches `Settled`, use the `cost` and `kwh` alongside your `reference` to charge your user or record the expense against the right account.
The `reference` must be set when creating the session. It cannot be changed afterwards.
## Polling and the `stateChanges` audit trail
Poll the session endpoint to track progress. The `stateChanges` array on the session object provides a timestamped record of every state transition, useful for debugging, auditing, and surfacing timeline information to users.
Recommended polling intervals:
- During `Starting` or `Stopping`: every 10–15 seconds
- During `Started`: every 30–60 seconds (energy and cost may update as charging progresses, though not all networks report these in real time)
- After `Stopped`: poll periodically until `Settled`, some networks settle quickly, others take longer
## Energy and cost availability
The `kwh` and `cost` fields on a session may be `null` until the session settles. Whether a charge point reports live energy data during charging depends on the network and station: some report it in real time, others only provide final values at settlement.
Do not assume energy or cost data is available mid-session. Always use the settled values for billing.
**The `cost` value is always excluding VAT.** It represents the raw, net price as reported by the charging network. If your product needs to present or charge VAT to your users, you are responsible for calculating and applying it on top of this figure.
## Settlement
Once charging ends, the network sends final billing data and the session transitions to `Settled` with confirmed `cost`, `kwh`, and `currency` values. Settlement is not immediate: it can take seconds to days depending on the network. Use the settled values for billing.
For a full explanation of how settlement works, how to detect corrections using `settlementVersion`, and billing implications, see [Settlement & Corrections](/docs/charge-now/guides/sessions/settlement).
---
> Source: https://technology.road.io/docs/charge-now/guides/sessions/settlement
# Settlement & Corrections
## What settlement means
When charging ends, the charging network sends **final billing data** containing the confirmed energy consumed and the total cost. This is settlement. When ChargeNow receives this data it transitions the session from `Stopped` to `Settled` and writes the confirmed `cost`, `kwh`, and `currency` to the session.
**Settlement is not immediate.** How long it takes depends entirely on the charging network. Most networks settle within seconds to a few minutes. Some slower or legacy networks can take hours or even days. Design your billing flow to wait for `Settled` before charging your users.
## The `settlementVersion` field
Every session exposes a `settlementVersion` counter:
| Value | Meaning |
| ------------- | ------------------------------------------------- |
| absent | Session not yet settled |
| `1` | Settled for the first time |
| `2`, `3`, ... | Each subsequent correction increments the version |
Use `settlementVersion` to detect whether the figures you previously stored have been superseded. If you recorded a session at `settlementVersion: 1` and a later read or webhook event shows `settlementVersion: 2`, a correction has been applied and the `cost` or `kwh` values have changed.
## Settlement corrections
Most sessions settle once and the values never change. Some charging networks, however, send a correction after the initial settlement to fix a metering error, revise a tariff, or correct a conversion rate. When a correction arrives:
1. The session remains in `Settled` (the state itself does not change)
2. The `cost`, `kwh`, and/or `currency` fields are updated to the corrected values
3. `settlementVersion` increments by 1
4. A `session.updated` webhook fires with the new values
ChargeNow accepts corrections within **180 days** of the first settlement. Corrections arriving after that window are recorded internally for audit purposes but do not update the session.
## Practical guidance
- **Wait for `Settled` before billing.** The `cost` and `kwh` values are not confirmed until the session reaches `Settled`.
- **Store `settlementVersion` alongside the session record.** If a later read or webhook event shows a higher version, update your records and re-run any downstream billing logic.
- **Use [webhooks](/docs/charge-now/guides/webhooks/session-updated) to detect corrections without polling.** Every settlement, including corrections, fires a `session.updated` event with the updated `settlementVersion`.
- **Corrections are uncommon.** Design your happy path for the simple one-settlement case and handle corrections gracefully.
## Billing implications
ChargeNow invoices your integration based on the settled values. If a correction increases the cost, the difference is added to your next invoice. If it decreases the cost, a credit is applied to your account.
The correction window closes 180 days after the first settlement. After that point the values will not change and your invoice will not be revised for that session.
---
> Source: https://technology.road.io/docs/charge-now/guides/webhooks
# Webhooks
## What webhooks are for
Webhooks push notifications to an HTTPS endpoint you control whenever something happens in your integration that you care about. Each notification is delivered as a signed HTTP POST containing a JSON payload. Webhooks let you react in near real time without polling the REST API.
This page describes the parts of webhook delivery that are the same for every event: the envelope shape, the signature scheme, retries, idempotency, and ordering. The per-event payload (what `data` actually contains) is documented on the sub-page for each event type, and machine-readable in the [Webhooks reference](/docs/charge-now/reference/chargenow-api/webhooks).
## Available events
| Event type | Description |
| --------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| [`session.updated`](/docs/charge-now/guides/webhooks/session-updated) | Fired whenever a charging session changes state or its energy/cost figures change. |
More event types will be introduced over time. Each one will be documented on its own sub-page.
## Getting set up
Webhook configuration is not self-serve. To start receiving events, contact us with:
1. **The HTTPS endpoint** that should receive deliveries.
2. **The event types** you want to subscribe to.
3. **A short description** (optional) so we can label the webhook in our system.
We will:
1. Create the webhook against your integration.
2. Generate a signing secret of the form `whsec_...` and share it with you over a secure channel.
3. Send a synthetic test event to your endpoint so you can verify the receiver before any real traffic flows. The synthetic event goes through the exact same delivery pipeline, signature, and retry policy as a production event.
Keep the secret somewhere your application can read it (a secrets manager, an env var). Anyone with the secret can forge a delivery, so treat it like an API key.
## The delivery envelope
ChargeNow delivers a [CloudEvents 1.0](https://github.com/cloudevents/spec/blob/v1.0/spec.md) envelope as the HTTP body. The shape is the same for every event; only `data` differs, carrying the event-specific payload. Field by field, the envelope and each payload are in the [Webhooks reference](/docs/charge-now/reference/chargenow-api/webhooks).
```
{
"specversion": "1.0",
"id": "5b9d6c1a-3d2f-5e88-9c1f-1a5b2e0e8e1a",
"type": "session.updated",
"source": "charge-now/api",
"time": "2026-06-15T10:14:22.482931Z",
"datacontenttype": "application/json",
"data": { ...event-specific fields... }
}
```
Two fields matter for delivery handling: deduplicate on the top-level `id` (stable across retries, and the only id the signature covers), and order events for the same resource by `time` (a later `time` wins). New optional keys may appear over time; ignore unknown ones.
## Headers
```
Content-Type: application/json
Webhook-Id:
Webhook-Event:
Webhook-Timestamp: 1781234567
Webhook-Signature: 5f3b9c... (hex-encoded HMAC-SHA256)
```
The [reference](/docs/charge-now/reference/chargenow-api/webhooks) documents each header. Two matter here: `Webhook-Timestamp` and `Webhook-Signature` drive signature verification (below). `Webhook-Id` is handy for support requests but is not signed, so deduplicate on the envelope `id`, not on it.
## Verifying the signature
Always verify the signature **before** trusting any field in the body. The verification recipe is:
1. Read the raw request body as bytes. Do **not** parse and re-serialise it first, JSON formatting differences will break the signature.
2. Read the `Webhook-Timestamp` header.
3. Compute `HMAC-SHA256(secret, ".")` and hex-encode it.
4. Compare with the `Webhook-Signature` header using a constant-time comparison.
5. Reject the request if the comparison fails, or if the timestamp is older than a sensible window (we recommend 5 minutes).
Any language with a standard crypto library exposes the primitives you need: an HMAC-SHA256 implementation (e.g. `crypto/hmac` in Go, `crypto` in Node.js, `hmac` in Python, `javax.crypto.Mac` in Java) and a constant-time byte comparison (e.g. `hmac.Equal`, `crypto.timingSafeEqual`, `hmac.compare_digest`). Do not use a plain `==` on the digest, a naive comparison can leak the secret over time through timing side channels.
Two implementation details worth highlighting:
- **Capture the raw request body before any middleware parses it.** Most web frameworks expose a way to read the body as bytes (Express's `express.raw`, Go's `io.ReadAll(r.Body)`, Flask's `request.get_data()`, etc.). Parse the JSON only after the signature has verified.
- **Build the signing input as `"."`** with a single ASCII `.` between the two parts. Feed it into HMAC-SHA256 keyed by your secret, hex-encode the digest, then compare.
## How to respond
| Your response | What ChargeNow does |
| --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `2xx` | Treats the delivery as successful and does not retry. |
| `408 Request Timeout`, `425 Too Early`, `429 Too Many Requests` | Retries with exponential backoff. |
| Any other `4xx` | Stops retrying. The delivery is considered permanently failed. Use this for "I will never accept this" cases (e.g. signature mismatch is your bug, not ours). |
| `5xx`, connection error, TLS error, read timeout | Retries with exponential backoff. |
Respond quickly. The HTTP call has a **30 second timeout per attempt**, after which we treat the attempt as failed and retry. Acknowledge the delivery first and process the event asynchronously (queue, background job) if your handler takes longer.
## Retries
Failed attempts are retried with exponential backoff and jitter, capped at 30 minutes between attempts, up to **25 attempts spread over roughly 9.5 hours**. The first few intervals are approximately:
```
5s, 15s, 45s, 2m15s, 6m45s, 20m15s, then 30m for the remaining attempts
```
After 25 attempts the delivery is dropped. There is no automated replay. If your endpoint was down for longer than the retry window, reconcile via the REST API.
## Idempotency and duplicates
ChargeNow guarantees **at-least-once** delivery. The same event may arrive more than once for legitimate reasons (a retry that completed on our side after a network blip, an upstream reprocess). Build your receiver to be idempotent.
The recommended strategy:
- Use the envelope `id` (top-level CloudEvents `id`, **not** the `Webhook-Id` header) as your deduplication key.
- The `id` is stable across all retries of the same logical event and is the only id protected by the signature.
- Track processed ids for at least the length of the retry window (10 hours is a safe minimum, 24 hours is comfortable).
## Ordering and race conditions
Webhooks are **not strictly ordered**. Two events for the same resource can arrive out of order, particularly if the first one had to be retried while the second went through on the first attempt. This is normal industry behaviour and not specific to ChargeNow.
Practical rules:
- **Treat each event as a snapshot, not a delta.** The `data` payload reflects the resource's known state at the time the event was produced.
- **Use the envelope `time` field to detect stale events.** If you already processed an event with a later `time` for the same resource, skip the older one.
- **Do not assume events arrive in the order they happened.** A later `time` always wins.
A robust handler in pseudocode:
```
on event:
if seen(event.id): return 200
record = load(resource_id from event.data)
if record.last_event_time and record.last_event_time >= event.time:
mark_seen(event.id)
return 200
apply(event.data)
record.last_event_time = event.time
mark_seen(event.id)
return 200
```
Per-event-type ordering nuances (e.g. settlement corrections for `session.updated`) are documented on each event's sub-page.
## Reconciliation
Webhooks are an optimisation over polling, not a replacement for the source of truth. If correctness matters (billing, accounting, compliance), reconcile periodically by reading the underlying resource via the REST API. The REST response is authoritative, the webhook payload reflects the same data at the moment the event was produced.
## Disabling a webhook
To pause deliveries (e.g. during a deploy that touches your receiver), contact us and we will set the webhook to `disabled`. In-flight retries continue until the webhook is re-enabled or the retry window expires. To resume, contact us and we will set it back to `active`.
## Rotating the secret
To rotate the signing secret, contact us. We will create a new webhook with a fresh secret and disable the old one once you have cut over. Plan a brief overlap where your receiver accepts either secret during the cutover.
## Common pitfalls
- **Parsing the body before verifying the signature.** Always verify against the raw bytes. Any whitespace or key-order change breaks the HMAC.
- **Using non-constant-time comparison.** Use `hmac.compare_digest`, `crypto.timingSafeEqual`, or your language's equivalent. A naive `==` can leak the secret over time.
- **Assuming a single delivery per event.** Retries and at-least-once delivery mean duplicates happen, dedupe on the envelope `id`.
- **Assuming strict order.** Compare the envelope `time` against what you have already recorded.
- **Letting the handler block.** Any single attempt that exceeds 30 seconds is retried. Acknowledge fast, process async.
- **Trusting `Webhook-Id` for idempotency.** It is convenient for support requests but is not protected by the signature. Use the envelope `id`.
## Support
If a delivery is failing and you cannot tell why, send us the `Webhook-Id` header from a failed attempt (or the envelope `id`) and we can look up the delivery in our system.
---
> Source: https://technology.road.io/docs/charge-now/guides/webhooks/session-updated
# session.updated
This page documents the `session.updated` event payload. For the envelope shape, headers, signature scheme, retries, and idempotency rules that apply to every webhook, see [Webhooks](/docs/charge-now/guides/webhooks).
## When it fires
`session.updated` is emitted whenever a charging session changes state or when its energy/cost figures are revised. In practice you will see one event per state transition (`Pending`, `Starting`, `Started`, `Stopping`, `Stopped`, `Settled`, `Failed`, `Abandoned`) and one event when a settlement correction lands.
## Payload shape
The `data` field is the public `Session` object, the same shape returned by `GET /sessions/{id}`, so you can deserialise it with the model you already use. The full envelope, delivery headers and payload are in the [session.updated reference](/docs/charge-now/reference/chargenow-api/webhooks#sessionupdatedwebhook), machine-readable. New keys may appear over time; ignore unknown ones to stay forward-compatible.
### Example: session that just started
```json
{
"specversion": "1.0",
"id": "5b9d6c1a-3d2f-5e88-9c1f-1a5b2e0e8e1a",
"type": "session.updated",
"source": "charge-now/api",
"time": "2026-06-15T10:14:22.482931Z",
"datacontenttype": "application/json",
"data": {
"id": "c5a7e1b2-4d3a-4c0e-b1f3-9c7b8d2e1a4f",
"chargePointId": "31a4f0c2-5d10-4a8d-9b3a-2e0f1c7d8b6a",
"state": "Started",
"reference": "order-1042",
"kwh": 4.2,
"cost": 1.78,
"currency": "EUR",
"startedAt": "2026-06-15T10:12:03Z"
}
}
```
### Example: session that has settled
```json
{
"specversion": "1.0",
"id": "e10cf2b4-7a89-5b66-9d44-0a3b9f4d2e7c",
"type": "session.updated",
"source": "charge-now/api",
"time": "2026-06-15T11:48:55Z",
"datacontenttype": "application/json",
"data": {
"id": "c5a7e1b2-4d3a-4c0e-b1f3-9c7b8d2e1a4f",
"chargePointId": "31a4f0c2-5d10-4a8d-9b3a-2e0f1c7d8b6a",
"state": "Settled",
"reference": "order-1042",
"kwh": 18.74,
"cost": 7.92,
"currency": "EUR",
"startedAt": "2026-06-15T10:12:03Z",
"stoppedAt": "2026-06-15T11:46:31Z",
"settlementVersion": 1
}
}
```
## What a handler acts on
The full payload is in the [reference](/docs/charge-now/reference/chargenow-api/webhooks#sessionupdatedwebhook). In practice a handler keys on three things: `data.id` to correlate (and the envelope `id` to dedupe), `data.state` to react to the [transition](/docs/charge-now/guides/sessions), and `data.settlementVersion` to catch a settlement correction, where a value higher than you last stored means `cost` and `kwh` have changed. Remember `cost` excludes VAT and can be `null` until the network reports figures.
## Ordering considerations
Webhooks are at-least-once and not strictly ordered (see [Ordering and race conditions](/docs/charge-now/guides/webhooks#ordering-and-race-conditions) on the main webhooks page). For `session.updated` specifically:
- **Use the envelope `time` field** to determine which event is newer for a given `data.id`. The session state machine is monotonic for a single session except for settlement corrections (below), so a later `time` always wins.
- You may see `Settled` arrive before `Stopped` if the earlier delivery was retried. The envelope `time` reflects the order in which events were produced on our side, so use it as the source of truth.
## Settlement corrections
In rare cases the charging network sends a correction after the initial settlement, for example to fix a metering error or revise a tariff. When that happens, a session that already reached `Settled` emits another `session.updated` event, still with `state: "Settled"` but with updated `cost`, `currency`, `kwh`, and an incremented `settlementVersion`.
- **Use `settlementVersion` to detect corrections.** If you receive a `session.updated` event with a higher `settlementVersion` than you last recorded, the `cost` and `kwh` have changed and you should update your records.
- Corrections are accepted within **180 days** of the first settlement. After that window the values will not change.
- Compare the envelope `time` against the `time` of the last event you applied for the same session and take the later one. The `data` payload always reflects the latest confirmed figures.
- See [Settlement & Corrections](/docs/charge-now/guides/sessions/settlement) for the full explanation, cancellation corrections, and billing implications.
---
> Source: https://technology.road.io/docs/support-agent-mcp/overview
# Support Agent MCP
The Support Agent MCP server is how you build support agents on Road. It gives an agent a defined set of tools to answer customer questions and act on the platform on a customer's behalf, over the Model Context Protocol.
The documentation has three parts:
- **Overview** (this page) - connecting, access, and the access model.
- **[Customer verification](/docs/support-agent-mcp/customer-verification)** - how an agent verifies a customer and threads the conversation token.
- **Tools** - the tools available, as a reference, split by area: [Charging stations](/docs/support-agent-mcp/charge-points), [Charging cards](/docs/support-agent-mcp/charging-cards) and [Customer](/docs/support-agent-mcp/customer).
## Endpoint
The partner MCP server is at:
```
https://partner-mcp.road.io
```
Connect to it as an MCP client. Every request is authenticated with a client token (`Authorization: Bearer `), which identifies the client and the provider tree it may reach.
## Getting access
Access is granted per client. To have a client registered and the tools you need enabled, **contact your Road account support manager**. They will set up the client, scope it to the right providers, and enable the tools for your integration. A client only ever sees the tools it has been granted.
## Tool naming
Tool names are `-`, hyphenated and globally unique, for example `charge-points-find` and `customer-verify`. The category matches the tool's group in the [reference](/docs/support-agent-mcp/charge-points).
## What a tool can see
Access is layered. A call passes through each gate before it reaches a tool:
1. **Client authentication.** Every request carries the client token, which resolves the provider tree the client may reach (a root provider and all of its children).
2. **Per-client tool access.** An operator enables each tool per client (see Getting access above). A tool that is not enabled is hidden from tool listings and rejected if called.
3. **Provider scoping.** Provider-scoped data is filtered to the client's providers. Anything outside that tree is invisible.
4. **Caller identity.** Some tools require a verified customer attached to the conversation. A caller with account-level read access to a resource sees it across the whole account; other callers see only their own users, cards, charging stations and home-charging sessions. A colleague's home charging station (employee reimbursement) is never shown, whatever the caller's access. A lookup that falls outside your scope returns the same "not found" response as a record that does not exist, so ownership is never revealed.
## Client vs customer
Because the client is always authenticated, each tool's **Access** line in the reference is about the *customer*, not the client: whether the tool needs a verified customer attached to the conversation.
- **No customer verification** tools work without one, for example a person standing at a station, or a first-line card check.
- **Requires a verified customer** tools need the customer to be verified first. See [Customer verification](/docs/support-agent-mcp/customer-verification).
## Read-only and state-changing tools
Most tools are read-only. A few change state or act on hardware, and are flagged in the reference:
- `charge-points-reboot` sends a reset to a station, behind a server-side safety check.
- `charge-points-unlock` releases a stuck cable.
- `customer-request-verification`, `customer-verify` and `customer-authorize` create or upgrade a conversation.
---
> Source: https://technology.road.io/docs/support-agent-mcp/customer-verification
# Customer verification
Some tools work for anyone (a person standing at a station, a first-line card check). Others read a customer's own data and need that customer to be **verified** first. Verification attaches a customer's identity to the conversation, and that identity is carried between calls by a **conversation token**.
## The conversation token
The conversation token is an opaque handle for one conversation. It is how the server remembers who has been verified across a series of tool calls.
- It is returned by `customer-request-verification` and refreshed by `customer-verify`.
- You **thread it back unchanged** on every subsequent call in the conversation.
- It is handled at the transport layer, not as a normal tool argument, so it does not appear in the parameter tables in the [reference](/docs/support-agent-mcp/charge-points). The server reads it and strips it before a tool runs.
- On tools that require a verified customer it is mandatory; on the others it is optional, and some tools enrich their output when it is present (for example `charge-points-status` will tell you whether the most recent authorisation attempt used one of the customer's own tokens).
## The flow
1. **Request a code.** Call `customer-request-verification` with the customer's email or user ID and a channel (`email` or `sms`). It sends a six-digit code (valid for five minutes) and returns a `conversationToken`.
2. **Verify the code.** Call `customer-verify` with the code, threading the token back. On success the conversation is upgraded to authorised and a **refreshed** `conversationToken` is returned. Replace the previous token with this one.
3. **Use customer-scoped tools.** Keep threading the latest token. Tools that require a verified customer now run, scoped to what that customer may see.
```flow
step | customer-request-verification | { conversationToken, expiresAt, … }
note | The customer reads the six-digit code and gives it to the agent.
step | customer-verify (code + token) | { verified: true, conversationToken } (refreshed)
note | The conversation is now authorised. Replace the previous token with this one.
step | charge-points-details / charging-cards-details / … | Runs, scoped to the verified customer, with the latest token threaded.
```
## Lifetime and limits
- The verification **code** expires five minutes after it is sent.
- After a successful verification the **conversation** stays authorised on a rolling 30-minute idle window; keep using it and it stays alive.
- A customer gets up to **five attempts** at the code before the challenge locks out; request a new code to start over.
- If the customer mistyped their email or phone, call `customer-request-verification` again with the same `conversationToken` to reuse the conversation rather than starting a new one.
## Trusted clients: skipping the code
A client that has already established the customer's identity by other means can be granted `customer-authorize`, which attaches a known user to a new authorised conversation **without** a one-time code. Because it bypasses verification, it is off by default and a Road operator must explicitly enable it for the client. Treat it as a trusted-client feature, not a general tool.
## Related tools
The tool signatures for this flow are in the reference under [Customer](/docs/support-agent-mcp/customer): `customer-request-verification`, `customer-verify`, `customer-authorize`, and the account-wide `customer-summary`.
---
> Source: https://technology.road.io/blog/async-exports
# How we fixed large exports
## The problem we had
When we first built CSV exports into the dashboard, they were a quick HTTP request. The API service would query the database, format the rows, and stream the file back.
The process only took a few seconds. As the platform grew, tenants that used to export hundreds of rows were now exporting hundreds of thousands, still powered by the same code.
Users started to see a loading spinner for several minutes without any indication of progress, while the queries and formatting burned CPU on the same dashboard that powered everyone else's screens.
Even worse, for large enough data, the request simply timed out, wasting every row of the computed work. Frustrated users who were faced with a dead spinner long enough assumed the export failed and clicked again.
Every retry was another full export adding load to an already degraded API. Ironically, the feature broke first for the tenants who had the most to export.
## How most companies solve it
Managing long-running tasks is a well-known hurdle for SaaS products. The textbook answer for this problem is to stop handling exports inside the request.
Instead, we split the work into three phases:
- **Acknowledge** - Validate the request, dedupe on an idempotency key, create a *Job* in the database, and respond with a *Job ID*.
The UI displays a toast message that the request is worked on in the background.
- **Process** - Register the job on a durable queue. Workers pull jobs at their own pace: run the export query, stream the rows into a CSV, and upload the file to the storage bucket.
- **Notify** - Mark the job as completed and e-mail the user a link to the export file.
This split ensures the API service and the workers scale independently. The workers autoscale on queue depth, and a worker crash is retried by another worker instead of impacting the user.
Repeat clicks are cheap too: instead of kicking off another full export, the API sees one is already running and returns the existing job.
## How we leveraged Temporal
While comparing technologies to build the solution, we realised [Temporal](https://docs.temporal.io/), which we were already running in our stack, could do most of the heavy lifting for us.
Temporal retries failed workflows by design, ships with a durable task queue, and detects dead workers through heartbeats. Each workflow also keeps its full execution history, so a failed export comes with the evidence needed to debug it.
The export itself runs as a single activity: a failed attempt is retried automatically under the activity's retry policy, starting again from the beginning.
## What the user gets now
Requesting an export now returns an immediate confirmation while the work runs in the background, and the dashboard polls the job's status.
When the file is ready, a notification offers the download through a short-lived signed storage link, and a failed export is retried automatically.
The tenants with the largest data volumes, who previously could not complete an export at all, now get their files reliably, and the dashboard stays responsive for everyone else while an export runs.