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,MeterValuesfor OCPP 1.6,TransactionEventfor 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:
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.
The CSMES will initiate a request to CSMEC as follows:
POST /1/csmec/pairings (CSMEC API)
Sample request body:
{
"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.
The CSMEC will initiate a request to the CSMES as follows:
POST /1/pairings (CSMES API)
Sample request body:
{
"pairingCode": "4A6BH28"
}
Sample response body:
{
"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.
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:
{
"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:
{
"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. Depending on value of protocol message payload will conform to either OCPP 1.6 schemas or OCPP 2.0.1 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:
{
"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 errorNotSupported: The requested operation is not supportedSecurityError: Security validation failedFormationViolation: Request format validation failedPropertyConstraintViolation: Property value constraint violatedOccurrenceConstraintViolation: 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.
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:
{
"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:
{
"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.:
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.:
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.:
curl -XDELETE \
-H "X-Api-Key: $CSMEC_API_KEY" \
https://api.provider.com/1/csmec/pairings/045a6ffd-b16e-48fa-ab67-86ca4ccf8ffe