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:

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:

NameTypeDescriptionRequired?
idUUIDPairing ID, generated by CSMES when charging station pairing is establishedYes
pairingCodestringPairing code provided by the userYes
chargingStationChargingStationCharging station detailsYes

ChargingStation object:

NameTypeDescriptionRequired?
evsesEVSE[]List of charging station EVSEsYes

EVSE object:

NameTypeDescriptionRequired?
evseIdintEVSE identifier within a charging station: 1, 2 and so onYes
capabilitiesstring[]List of capabilities this EVSE supportsYes
connectorsConnector[]List of EVSE connectorsYes

Connector object:

NameTypeDescriptionRequired?
idintConnector identifier within an EVSE: 1, 2 and so onYes
powerTypestringConnector power type, one of: UNSPECIFIED, AC_1_PHASE, AC_3_PHASE, DCYes
maxVoltageintMax voltageYes
maxAmperageintMax amperageYes

Expected HTTP response codes:

CodeDescription
201Pairing successfully created
401Request is unauthorized, ensure that valid authentication credentials are provided
404Invalid or expired pairing code
409This pairing has already been created
5xxInternal 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:

CodeDescription
201Pairing successfully created
401Request is unauthorized, ensure that valid authentication credentials are provided
404Invalid or expired pairing code
409This pairing has already been created
5xxInternal 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:

CodeDescription
204Pairing successfully deleted
401Request is unauthorized, ensure that valid authentication credentials are provided
404Pairing not found
5xxInternal 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:

CodeDescription
204Pairing successfully deleted
401Request is unauthorized, ensure that valid authentication credentials are provided
404Pairing not found
5xxInternal 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:

NameTypeDescriptionRequired?
pairingIdUUIDPairing IDYes
protocolstringProtocol type, one of: ocpp1.5, ocpp1.6, ocpp2.0.1, ocpp2.1Yes
typestringMessage type, specific to the protocol used, e.g.: MeterValues, TransactionEvent, GetConfiguration:Response, etc.Yes
payloadobjectThe actual message body that's coming from a charging station (null when the charging station rejected a command)No
errorPayloadobjectError details when the charging station rejected a command (null otherwise)No
csmsResponsePayloadobjectThe complete CSMS response payload for successful calls (null for failed calls)No
csmsErrorPayloadobjectError 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 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:

CodeDescription
200Request successfully processed. Note that due to batch nature of this endpoint individual messages could still fail, refer to the table below
401Request is unauthorized, ensure that valid authentication credentials are provided
5xxInternal 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:

CodeDescription
BAD_REQUESTMessage payload or protocol is invalid
ENTITY_NOT_FOUNDPairing not found
ACCESS_DENIEDClient has no access to requested pairing
INTERNAL_ERRORInternal 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:

CodeDescription
200Request successfully processed. Note that due to batch nature of this endpoint individual messages could still fail, refer to the table below
401Request is unauthorized, ensure that valid authentication credentials are provided
5xxInternal 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:

CodeDescription
BAD_REQUESTMessage payload or protocol is invalid
ENTITY_NOT_FOUNDPairing not found
ACCESS_DENIEDClient has no access to requested pairing
SERVICE_UNAVAILABLECharging station is unavailable (temporarily disconnected), client may retry the message later
INTERNAL_ERRORInternal server error, client may retry the message later

Environments

Road exposes 2 CSME environments: staging and production. These are the base URLs of them:

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