DataTransfer - CSMS Developer Guide
Based on OCPP 2.1 Edition 2 Specification (Part 2), Section P (DataTransfer). This guide covers both DataTransfer flows (P01–P02), including sending and receiving vendor-specific data between the CSMS and Charging Stations.
1. Overview
IntroductionThe DataTransfer functional block enables exchange of vendor-specific, non-standard data between the CSMS and Charging Stations. It is a bidirectional message: either party can initiate a DataTransferRequest.
CSMS Roles in DataTransfer
Initiator (P01)
CSMS sends custom data to a Charging Station. Constructs and sends DataTransferRequest,
then processes the DataTransferResponse.
Responder (P02)
Charging Station sends custom data to CSMS. CSMS receives DataTransferRequest,
validates, and returns DataTransferResponse.
| Role | Use Case | CSMS Action |
|---|---|---|
| Initiator (P01) | CSMS sends custom data to Charging Station | Constructs and sends DataTransferRequest, processes DataTransferResponse |
| Responder (P02) | Charging Station sends custom data to CSMS | Receives DataTransferRequest, validates, and returns DataTransferResponse |
Extension Mechanisms in OCPP 2.1
Two extension mechanisms exist in OCPP 2.1:
DataTransferRequest / Response
Full custom message exchange for vendor-specific functionality. This is the mechanism covered in this guide.
CustomData Element
Optional element in all JSON schemas that allows additional properties on any standard type. Not covered here (see OCPP 2.1 Part 4).
Important Caveat
Use DataTransfer with extreme caution and only for optional functionality. It impacts compatibility with systems that do not implement the same vendor-specific extensions. Always verify that the functionality you need doesn't already exist in the standard OCPP 2.1 messages before resorting to DataTransfer.
2. Data Types & Enumerations
ReferenceDataTransferStatusEnumType
Defines the possible status values in a DataTransferResponse.
"Accepted" | "Rejected" | "UnknownMessageId" | "UnknownVendorId"| Value | When Used (CSMS as Responder) | When Received (CSMS as Initiator) |
|---|---|---|
Accepted | CSMS recognized the vendorId (and messageId if used) and successfully processed the request. | Charging Station processed the request successfully. Read data for results. |
Rejected | CSMS recognized the vendorId/messageId but is refusing the specific request (e.g., invalid data, business rule violation). | Charging Station understood but rejected the request. Check statusInfo for reason. |
UnknownVendorId | CSMS has no handler registered for the provided vendorId. Mandatory per P02.FR.06. | Charging Station does not support the vendor extension. Do not retry. |
UnknownMessageId | CSMS recognizes the vendorId but does not recognize the messageId. Mandatory per P02.FR.07. | Charging Station knows the vendor but not this message. Check messageId. |
StatusInfoType
Optional additional information returned with response statuses.
{
"reasonCode": string, // REQUIRED - Predefined code (max 20 chars, case-insensitive)
"additionalInfo": string // OPTIONAL - Human-readable detail text (max 1024 chars)
}| Field | Type | Required | Max Length | Description |
|---|---|---|---|---|
reasonCode | string | Yes | 20 | Predefined code for the reason. Case-insensitive. |
additionalInfo | string | No | 1024 | Human-readable detail text. |
CustomDataType
Optional custom extension data. Note that CustomDataType intentionally does not have additionalProperties: false, allowing arbitrary extra fields.
{
"vendorId": string // REQUIRED - Vendor identifier (max 255 chars)
// ... additional arbitrary fields allowed
}Constraints Summary
| Constraint | Value |
|---|---|
vendorId max length | 255 characters |
messageId max length | 50 characters |
data max length | Undefined (agree between parties) |
statusInfo.reasonCode max length | 20 characters |
statusInfo.additionalInfo max length | 1024 characters |
CustomDataType.vendorId max length | 255 characters |
Required fields in request | vendorId only |
Required fields in response | status only |
3. P01 — CSMS Sends DataTransfer to Charging Station
CSMS-Initiated| Use Case ID | P01 |
| Direction | CSMS → Charging Station |
| CSMS Role | Initiator (sends request, processes response) |
| OCPP Messages | DataTransferRequest / DataTransferResponse |
Flow Diagram
CSMS Charging Station
| |
| DataTransferRequest(vendorId, [messageId], [data]) |
|---------------------------------------------------->|
| |
| DataTransferResponse(status, [statusInfo], [data]) |
|<----------------------------------------------------|
| |Constructing the Request
The CSMS builds a DataTransferRequest with the following fields:
| Field | Type | Required | Max Length | Description |
|---|---|---|---|---|
vendorId | string | Yes | 255 | Identifies the vendor-specific implementation. Should use reversed DNS notation (e.g., com.mycompany.feature). |
messageId | string | No | 50 | Optional identifier for a specific message type or implementation within the vendor namespace. |
data | any | No | undefined | Arbitrary data payload. No type or length constraint in the schema. Can be a string, number, object, array, etc. |
customData | CustomDataType | No | - | Optional custom extension data (requires vendorId). |
{
"vendorId": "com.example.fleet",
"messageId": "getVehicleStatus",
"data": {
"vehicleId": "VIN-12345",
"requestTimestamp": "2025-06-15T10:30:00Z"
}
}Processing the Response
The Charging Station responds with a DataTransferResponse. The CSMS must handle all possible status values:
| Status Value | Meaning | CSMS Action |
|---|---|---|
Accepted | Request was understood and processed successfully. | Process the optional data field per your vendor-specific agreement. |
Rejected | Request was understood but explicitly rejected. | Handle rejection per vendor-specific agreement. Log the reason. |
UnknownVendorId | The Charging Station has no implementation for the given vendorId. | The station does not support this vendor extension. Do not retry with same vendorId to this station. |
UnknownMessageId | The vendorId is known, but the messageId is not recognized. | The station supports this vendor but not this specific message. Check messageId spelling/version. |
{
"status": "Accepted",
"data": {
"vehicleStatus": "charging",
"batteryLevel": 72
}
}{
"status": "UnknownVendorId",
"statusInfo": {
"reasonCode": "UnknownVendor",
"additionalInfo": "Vendor com.example.fleet is not supported by this station."
}
}OCPP WebSocket Wire Format
The DataTransferRequest is sent as an OCPP CALL message over WebSocket:
[2, "msg-uuid-001", "DataTransfer", {
"vendorId": "com.example.fleet",
"messageId": "getVehicleStatus",
"data": { "vehicleId": "VIN-12345", "requestTimestamp": "2025-06-15T10:30:00Z" }
}]Wire Format Fields
2= CALL message type"msg-uuid-001"= unique message ID for correlation"DataTransfer"= OCPP action name
Requirements Checklist for P01
| Req ID | Requirement | Implementation Guidance |
|---|---|---|
P01.FR.01 | Only use DataTransferRequest for functions not supported by standard OCPP. | Before implementing a DataTransfer, verify the functionality doesn't already exist in OCPP 2.1. |
P01.FR.02 | vendorId should use reversed DNS namespace (e.g., com.example.feature). | Use your organization's reversed domain as prefix. |
P01.FR.03 | messageId may be used to indicate a specific message or implementation. | Define a registry of messageIds per vendorId in your vendor documentation. |
P01.FR.04 | Length of data is undefined; recommended to agree on it between parties. | Document the expected payload schema per messageId in your vendor-specific spec. |
P01.FR.05 | If recipient has no implementation for the vendorId, it returns UnknownVendorId. | Handle this status in your response processing — the station doesn't support this vendor. |
P01.FR.06 | If messageId mismatches, recipient returns UnknownMessageId. | Handle this status — the vendorId is known but the specific message is not. |
P01.FR.07 | Usage of Accepted/Rejected and data is part of vendor-specific agreement. | Define and document your own contract for what these mean per messageId. |
4. P02 — CSMS Receives DataTransfer from Charging Station
CS-Initiated| Use Case ID | P02 |
| Direction | Charging Station → CSMS |
| CSMS Role | Responder (receives request, must send response) |
| OCPP Messages | DataTransferRequest / DataTransferResponse |
Flow Diagram
Charging Station CSMS
| |
| DataTransferRequest(vendorId, [messageId], [data])
|------------------------------------------->|
| | ← CSMS must validate
| | and respond
| DataTransferResponse(status, [statusInfo], [data])
|<-------------------------------------------|
| |Incoming Request: DataTransferRequest
The CSMS receives a DataTransferRequest from a Charging Station with the following fields:
| Field | Type | Required | Max Length | Description |
|---|---|---|---|---|
vendorId | string | Yes | 255 | Identifies the vendor-specific implementation. |
messageId | string | No | 50 | Identifies the specific message type within the vendor namespace. |
data | any | No | undefined | Arbitrary payload. Can be any JSON type. |
customData | CustomDataType | No | - | Optional custom extension data. |
{
"vendorId": "com.chargervendor.diagnostics",
"messageId": "temperatureAlert",
"data": {
"connectorId": 1,
"temperature": 85.5,
"unit": "celsius",
"timestamp": "2025-06-15T14:22:00Z"
}
}CSMS Validation & Response Logic
The CSMS must follow this decision logic when processing incoming DataTransferRequest messages:
Receive DataTransferRequest
|
v
+-------------------------+
| Is vendorId recognized? |
+----------+--------------+
|
No | Yes
| | |
v | v
Return | +------------------------------+
status: | | Is messageId provided and |
"Unknown| | recognized (if applicable)? |
VendorId| +-------------+----------------+
" | | |
| No | Yes |
| | | | |
| v | v |
| Return | +---------------------+
| status: | | Process vendor- |
| "Unknown| | specific logic |
| MessageId +----------+----------+
| " | |
| | +----+-----+
| | Success? Failure?
| | | |
| | v v
| | Return Return
| | status: status:
| | "Accepted" "Rejected"
| | + [data] + [statusInfo]Building the Response: DataTransferResponse
The CSMS must always respond with a DataTransferResponse:
| Field | Type | Required | Description |
|---|---|---|---|
status | DataTransferStatusEnumType | Yes | One of: Accepted, Rejected, UnknownMessageId, UnknownVendorId |
statusInfo | StatusInfoType | No | Additional information about the status. |
data | any | No | Arbitrary response data per vendor-specific agreement. |
customData | CustomDataType | No | Optional custom extension data. |
Response Examples by Status
{
"status": "Accepted",
"data": {
"acknowledged": true,
"action": "logged"
}
}{
"status": "Rejected",
"statusInfo": {
"reasonCode": "InvalidData",
"additionalInfo": "Temperature value out of expected range."
}
}{
"status": "UnknownVendorId",
"statusInfo": {
"reasonCode": "UnknownVendor",
"additionalInfo": "Vendor 'com.chargervendor.diagnostics' is not registered in this CSMS."
}
}{
"status": "UnknownMessageId",
"statusInfo": {
"reasonCode": "UnknownMessage",
"additionalInfo": "messageId 'temperatureAlert' is not a recognized message for vendor 'com.chargervendor.diagnostics'."
}
}Requirements Checklist for P02
| Req ID | Requirement | Implementation Guidance |
|---|---|---|
P02.FR.01 | The vendorId should be known and uniquely identify the vendor implementation. | Maintain a registry/map of supported vendorIds in your CSMS. |
P02.FR.02 | Only used for functions not supported by standard OCPP. | This is the Charging Station's responsibility, but the CSMS should validate accordingly. |
P02.FR.03 | vendorId should use reversed DNS notation. | Validate format if desired, but do not reject based on format alone. |
P02.FR.04 | messageId may indicate a specific message or implementation. | Use as a sub-routing key within a vendorId namespace. |
P02.FR.05 | Length of data is undefined; recommended to agree between parties. | Validate payload size against your vendor-specific contract. |
P02.FR.06 | If CSMS has no implementation for the vendorId, return UnknownVendorId. | Mandatory. Implement vendorId lookup. If not found, respond with UnknownVendorId. |
P02.FR.07 | If messageId mismatches (when used), return UnknownMessageId. | Mandatory. If vendorId is known but messageId is not recognized, respond with UnknownMessageId. |
P02.FR.08 | Usage of Accepted/Rejected and data is per vendor-specific agreement. | Define and document your vendor-specific contracts. |
5. Message Schemas
ReferenceDataTransferRequest JSON Schema
{
"$schema": "http://json-schema.org/draft-06/schema#",
"$id": "urn:OCPP:Cp:2:2025:1:DataTransferRequest",
"type": "object",
"additionalProperties": false,
"properties": {
"vendorId": {
"description": "This identifies the Vendor specific implementation",
"type": "string",
"maxLength": 255
},
"messageId": {
"description": "May be used to indicate a specific message or implementation.",
"type": "string",
"maxLength": 50
},
"data": {
"description": "Data without specified length or format. This needs to be decided by both parties (Open to implementation)."
},
"customData": {
"$ref": "#/definitions/CustomDataType"
}
},
"required": ["vendorId"]
}Key Observations
vendorIdis the only required field.datahas no type constraint — it accepts any valid JSON value (string, number, boolean, object, array, null).additionalProperties: falsemeans no fields beyond the four defined above are allowed at the top level.
DataTransferResponse JSON Schema
{
"$schema": "http://json-schema.org/draft-06/schema#",
"$id": "urn:OCPP:Cp:2:2025:1:DataTransferResponse",
"type": "object",
"additionalProperties": false,
"properties": {
"status": {
"$ref": "#/definitions/DataTransferStatusEnumType"
},
"statusInfo": {
"$ref": "#/definitions/StatusInfoType"
},
"data": {
"description": "Data without specified length or format, in response to request."
},
"customData": {
"$ref": "#/definitions/CustomDataType"
}
},
"required": ["status"]
}Key Observations
statusis the only required field.datahas no type constraint, same as the request.statusInfoprovides optional structured error/info context.
Supporting Type Definitions
{
"type": "string",
"enum": ["Accepted", "Rejected", "UnknownMessageId", "UnknownVendorId"]
}{
"type": "object",
"additionalProperties": false,
"properties": {
"reasonCode": {
"type": "string",
"maxLength": 20
},
"additionalInfo": {
"type": "string",
"maxLength": 1024
},
"customData": {
"$ref": "#/definitions/CustomDataType"
}
},
"required": ["reasonCode"]
}{
"type": "object",
"properties": {
"vendorId": {
"type": "string",
"maxLength": 255
}
},
"required": ["vendorId"]
}
// Note: CustomDataType intentionally
// does NOT have
// "additionalProperties: false",
// allowing arbitrary extra fields.6. CSMS Decision Logic
ImplementationP02 — Handling Incoming DataTransferRequest
Pseudocode for handling incoming DataTransferRequest messages from Charging Stations:
function handleDataTransferRequest(request: DataTransferRequest): DataTransferResponse {
// Step 1: Vendor lookup (MANDATORY - P02.FR.06)
vendorHandler = vendorRegistry.get(request.vendorId)
if (vendorHandler == null) {
return {
status: "UnknownVendorId",
statusInfo: {
reasonCode: "UnknownVendor",
additionalInfo: "No handler for vendorId: " + request.vendorId
}
}
}
// Step 2: Message lookup (MANDATORY when messageId is used - P02.FR.07)
if (request.messageId != null) {
messageHandler = vendorHandler.getMessageHandler(request.messageId)
if (messageHandler == null) {
return {
status: "UnknownMessageId",
statusInfo: {
reasonCode: "UnknownMessage",
additionalInfo: "No handler for messageId: " + request.messageId
}
}
}
}
// Step 3: Process the vendor-specific logic
try {
result = messageHandler.process(request.data)
return {
status: "Accepted",
data: result // optional response data per vendor agreement
}
} catch (error) {
return {
status: "Rejected",
statusInfo: {
reasonCode: truncate(error.code, 20),
additionalInfo: truncate(error.message, 1024)
}
}
}
}P01 — Sending DataTransferRequest to Charging Station
Pseudocode for sending DataTransferRequest messages to Charging Stations:
function sendDataTransfer(
chargingStationId: string,
vendorId: string,
messageId?: string,
data?: any
): DataTransferResponse {
request = {
vendorId: vendorId // required, max 255 chars
}
if (messageId != null) {
request.messageId = messageId // optional, max 50 chars
}
if (data != null) {
request.data = data // optional, any JSON type
}
// Send via OCPP WebSocket as CALL [2, uniqueId, "DataTransfer", request]
response = ocppConnection.send(chargingStationId, "DataTransfer", request)
// Handle response
switch (response.status) {
case "Accepted":
// Success - process response.data per vendor agreement
return response
case "Rejected":
// Station understood but rejected - check statusInfo
log.warn("DataTransfer rejected", response.statusInfo)
return response
case "UnknownVendorId":
// Station does not support this vendor extension
log.warn("Station does not support vendorId: " + vendorId)
return response
case "UnknownMessageId":
// Station supports vendor but not this message
log.warn("Station does not support messageId: " + messageId)
return response
}
}7. Implementation Notes & Best Practices
GuideVendorId Convention
Use reversed DNS notation for vendorId values to ensure global uniqueness and avoid collisions between different vendors:
| Format | Example |
|---|---|
com.<company>.<feature> | com.example.fleet |
org.<organization>.<module> | org.openchargealliance.test |
Structured Data Field
Although the data field has no schema constraint, it is strongly recommended to use structured JSON objects. This allows for:
- Schema validation on your application layer
- Versioning of the custom protocol
- Easier debugging and logging
{
"vendorId": "com.example.ice",
"messageId": "iceParkedAtCs",
"data": {
"start_time": "2020-04-01T11:01:02"
}
}CSMS Vendor Handler Architecture
Implement a registry pattern to handle incoming DataTransfer messages:
CSMS DataTransfer Handler
+-- Vendor Registry (Map<vendorId, VendorHandler>)
| +-- "com.vendorA.feature" -> VendorAHandler
| | +-- "messageType1" -> handler function
| | +-- "messageType2" -> handler function
| | +-- (unknown messageId) -> return UnknownMessageId
| +-- "com.vendorB.diagnostics" -> VendorBHandler
| | +-- ...
| +-- (unknown vendorId) -> return UnknownVendorIdError Handling Notes
- The specification states error handling is n/a for both P01 and P02. This means OCPP-level errors (like CALLERROR for malformed JSON, etc.) are handled by the underlying OCPP transport layer, not by the DataTransfer logic.
- Application-level errors within your vendor-specific logic should be communicated via the
Rejectedstatus with descriptivestatusInfo.
Wire-Level Message Examples
Complete OCPP WebSocket wire-level examples for both flows:
[2, "19223201", "DataTransfer", {
"vendorId": "com.example.fleet",
"messageId": "getVehicleStatus",
"data": {
"vehicleId": "VIN-12345"
}
}][3, "19223201", {
"status": "Accepted",
"data": {
"vehicleStatus": "charging",
"batteryLevel": 72
}
}][2, "abc-123-def", "DataTransfer", {
"vendorId": "com.chargervendor.diagnostics",
"messageId": "temperatureAlert",
"data": {
"connectorId": 1,
"temperature": 85.5,
"unit": "celsius"
}
}][3, "abc-123-def", {
"status": "Accepted",
"data": {
"acknowledged": true
}
}]Wire Format Reference
[2, ...]= CALL message (request)[3, ...]= CALLRESULT message (response)- The second element is the unique message ID used for request/response correlation