The OCPI protocol is based on HTTP and uses the JSON format. It follows a RESTful architecture for web services where possible.
The interfaces are protected on the HTTP transport level, with SSL and token-based authentication. Please note that this mechanism does not require client-side certificates for authentication, only server-side certificates to set up a secure SSL connection.
Every OCPI HTTP request MUST add an 'Authorization' header. The header looks as follows:
Authorization: Token ZWJmM2IzOTktNzc5Zi00NDk3LTliOWQtYWM2YWQzY2M0NGQyCg==
Note
|
HTTP header names are case-insensitive |
The literal 'Token' indicates that the token-based authentication mechanism is used, in OCPI this is called the 'credentials token'. 'Credentials tokens' are exchanged via the credentials module. These are different 'tokens' than the Tokens exchanged via the Token Module: Tokens used by drivers to authorize charging. To prevent confusion, when talking about the token used here in the HTTP Authorization header, call them: 'Credentials Tokens'.
After the literal 'Token', there SHALL be one space, followed by the 'encoded token'. The encoded token is obtained by encoding the credentials token to an octet sequence with UTF-8 and then encoding that octet sequence with Base64 according to RFC 4648.
So for example, to use the credentials token 'example-token' in an OCPI request, one should include this header:
Authorization: Token ZXhhbXBsZS10b2tlbgo=
Note
|
Many OCPI 2.1.1 and 2.2 implementations do not Base64 encode the credentials token when including it in the 'Authorization' header. Since OCPI 2.2-d2 the OCPI specification documents clearly require Base64 encoding the credentials token in the header value. Implementations that wish to be compatible with non-encoding 2.1.1 and 2.2 implementations have to choose the right way to parse and write authorization headers by either trial and error or configuration flags. |
The credentials token must uniquely identify the requesting party. This way, the server can use the information in the Authorization header to link the request to the correct requesting party’s account.
If the header is missing or the credentials token doesn’t match any known party then the server SHALL respond with an HTTP 401 - Unauthorized
status code.
When a server receives a request with a valid CREDENTIALS_TOKEN_A
, on another module
than: credentials
or versions
,
the server SHALL respond with an HTTP 401 - Unauthorized
status code.
OCPI supports both Pull and Push models.
-
Push: Changes in objects and new objects are sent (semi) real-time to the receiver.
-
Pull: Receiver request a (full) list of objects periodically.
OCPI doesn’t require parties to implement Push. Pull is required, a receiver needs to be able to get in-sync after a period of connection loss.
It is possible to implement a Pull only OCPI implementation, it might be a good starting point for an OCPI implementation. However, it is strongly advised to implement Push for production systems that have to handle some load, especially when several clients are requesting long lists frequently. Push implementations tend to use fewer resources. It is therefore advised to clients pulling lists from a server to do this on a relative low polling interval: think in hours, not minutes, and to introduce some splay (randomize the length of the poll interface a bit).
The request method can be any of GET, POST, PUT, PATCH or DELETE. The OCPI protocol uses them in a way similar to REST APIs.
Method | Description |
---|---|
Fetches objects or information. |
|
POST |
Creates new objects or information. |
Updates existing objects or information. |
|
Partially updates existing objects or information. |
|
DELETE |
Removes existing objects or information. |
The HTTP header: Content-Type SHALL be set to application/json
for any request that contains a message body: POST, PUT and PATCH.
When no body is present, probably in a GET or DELETE, then the Content-Type header MAY be omitted.
A server is not required to return all objects to a client, the server might for example not send all CDRs to a client, because some CDRs do not belong to this client.
When a client receives objects from the server that contain invalid JSON or invalid OCPI objects (For example: missing fields), the client has no way of letting this know to the server. It is advised to log these errors and contact the server administrator about this. When a list of objects contains some objects that are correct and some with 'problems' the client should at least process the correct OCPI objects.
All GET methods that return a list of objects have pagination, this allows a client and server to control the number of objects returned in the response to a GET request, while still enabling the client to retrieve all objects by doing multiple requests with different parameters. Without pagination, the server has to return all objects in one response that could potentially contain millions of objects.
To enable pagination of the returned list of objects, additional URL parameters are allowed for the GET request and additional headers need to be added to the response.
The following table lists all the parameters that have to be supported but might be omitted by a client request.
Parameter | Datatype | Description |
---|---|---|
date_from |
Only return objects that have |
|
date_to |
Only return objects that have |
|
offset |
int |
The offset of the first object returned. Default is 0 (the first object). |
limit |
int |
The maximum number of objects to GET. The server might decide to return fewer objects, either because there are no more objects, or the server limits the maximum number of objects to return. This is to prevent, for example, overloading the system. |
The date_from
is inclusive and date_to
exclusive, this way, when sequential requests with to the same end-point are done,
the next interval will have no overlap and the date_from
of the next interval is simply the date_to
of the previous interval.
Example: With offset=0 and limit=10 the server shall return the first 10 records (if 10 objects match the request). Then the next page starts with offset=10.
For pagination to work correctly, it is important that multiple calls to the same URL (including query parameters):
result in the same objects being returned by the server.
For this to be the case, the sequence of objects mustn’t change, or as little as possible.
It is best practice to return the oldest objects first, that is, order the objects by creation date ascending.
While a client crawls over the pages (multiple GET requests every time to the 'next' page Link), a new object might be created on the server.
The client detects this: the X-Total-Count
will be higher on the next call.
Even so, the client does not have to retry any requests when this happens because only the last page will be different.
This means the client will not be required to crawl all pages all over again.
When the client has reached to last page it has retrieved all relevant pages and is up to date.
Note
|
Some query parameters can cause concurrency problems. For example the
date_to query parameter. When there are for example 1000 objects matching a
query for all objects with date_to before 2016-01-01. While crawling over the
pages one of these objects is updated. The client detects this: X-Total-Count
will be lower in the next request. It is advised to redo the previous GET with
the offset lowered by 1 (if the offset was not 0) and after that continue
crawling the 'next' page links.
|
HTTP headers that have to be added to any paginated GET response.
HTTP Header | Datatype | Description |
---|---|---|
Link |
String |
Link to the 'next' page should be provided when this is NOT the last page. The Link should also contain any filters present in the original request. See the examples below. |
X-Total-Count |
int |
(Custom HTTP Header) The total number of objects available in the server system that match the given query
(including the given query parameters, for example: |
X-Limit |
int |
(Custom HTTP Header) The maximum number of objects that the server can return. Note that this is an upper limit. If there are not enough remaining objects to return, fewer objects than this upper limit number will be returned, X-Limit SHALL then still show the upper limit, not the number of objects returned. |
Note
|
HTTP header names are case-insensitive |
Example of a required OCPI pagination link header:
Link: <https://www.server.com/ocpi/cpo/2.2.1/cdrs/?offset=150&limit=50>; rel="next"
After the client has called the given "next" page URL above the Link parameter will most likely look like this:
Link: <https://www.server.com/ocpi/cpo/2.2.1/cdrs/?offset=200&limit=50>; rel="next"
Example of a query with filters: Client does a GET to:
https://www.server.com/ocpi/cpo/2.2.1/cdrs/?date_from=2016-01-01T00:00:00Z&date_to=2016-12-31T23:59:59Z
The server should return (when the server has enough objects and the limit is the amount of objects the server wants to send is 100.) (This example should have been on 1 line, but didn’t fit the paper width.)
Link: <https://www.server.com/ocpi/cpo/2.2.1/cdrs/?offset=100
&limit=100&date_from=2016-01-01T00:00:00Z&date_to=2016-12-31T23:59:59Z>; rel="next"
Example of a server limiting the amount of objects returned: Client does a GET to:
https://www.server.com/ocpi/cpo/2.2.1/cdrs/?limit=2000
The server should return (when the server has enough objects and the limit is the amount of objects the server wants to send is 100.) The X-Limit
HTTP header should be set to 100 as well.
Link: <https://www.server.com/ocpi/cpo/2.2.1/cdrs/?offset=100&limit=100>; rel="next"
A PUT request must specify all required fields of an object (similar to a POST request). Optional fields that are not included will revert to their default value which is either specified in the protocol or NULL.
A PATCH request must only specify the object’s identifier (if needed to identify this object) and the fields to be updated. Any fields (both required or optional) that are left out remain unchanged.
The MIME-type of the request body is: application/json
and may contain the data as documented for each endpoint.
Normal client/server RESTful services work in a way where the Server is the owner of the objects that are created. The client requests a POST method with an object to the end-point URL. The response sent by the server will contain the URL to the new object. The client will request only one server to create a new object, not multiple servers.
Many OCPI modules work differently: the client is the owner of the object and only pushes the information to one or more servers for information sharing purposes. For example the CPO owns the Tariff objects and pushes them to a couple of eMSPs, so each eMSP gains knowledge of the tariffs that the CPO will charge them for their customers' sessions. eMSP might receive Tariff objects from multiple CPOs. They need to be able to make a distinction between the different tariffs from different CPOs.
The distinction between objects from different CPOs/eMSPs is made based on a {country_code} and {party_id}. The country_code’s and party_id’s of the parties on the other platform are received during the credentials handshake in the CredentialsRoles. The roles exchanged during the credentials handshake provide the server with details needed to determine which URLs a client might use.
Client Owned Object URL definition: {base-ocpi-url}/{end-point}/{country-code}/{party-id}/{object-id}
Example of a URL to a Client Owned Object
https://www.server.com/ocpi/cpo/2.2.1/tariffs/NL/TNM/14
POST is not supported for these kinds of modules. PUT is used to send new objects to the servers.
To identify the owner of data, the party generating the information that is provided to other parties via OCPI, a 'Data owner' is provided at the beginning of every module that has a clear owner.
When a client tries to access an object with a URL that has a different
country_code
and/or party_id
than one of the CredentialsRoles given during the
credentials handshake,
it is allowed to respond with an HTTP 404
status code, this way blocking client access to objects that do not belong to them.
When a client pushes a Client Owned Object, but the {object-id} in the URL is different from the id in the object being pushed, server implementations are advised to return an OCPI status code: 2001.
When doing a GET on the Sender interface of a module, the owner of an object can be determined by looking at the {country_code} and {party_id} in the object itself.
When one or more objects, returned in the response, do not meet one of the CredentialsRoles given during the credentials handshake, these objects may be ignored.
The content that is sent with all the response messages is an 'application/json' type and contains a JSON object with the following properties:
Property | Type | Card. | Description |
---|---|---|---|
data |
Array or Object or String |
* or ? |
Contains the actual response data object or list of objects from each request, depending on the cardinality of the response data, this is an array (card. * or +), or a single object (card. 1 or ?) |
status_code |
int |
1 |
OCPI status code, as listed in Status Codes, indicates how the request was handled. To avoid confusion with HTTP codes, OCPI status codes consist of four digits. |
status_message |
? |
An optional status message which may help when debugging. |
|
timestamp |
1 |
The time this message was generated. |
For brevity’s sake, any further examples used in this specification will only contain the value of the "data" field. In reality, it will always have to be wrapped in the above response format.
When a request cannot be accepted, the type response depends on the type of error. For more information see: Status codes
For errors on the HTTP layer, use HTTP error response codes, including the response format above, that contains more details. HTTP status codes are described on w3.org.
Note
|
Earlier versions of the OCPI 2.2.1 did not clearly specify what should be in the data field of the response format for every request. We advise that in cases where the specification does not explicitly specify what to put in the data field for the response to a certain request, the platform receiving the response accept both the data field being absent and the data field being present with any possible value. We also advise that in such cases, platform sending the response leave the data field unset in the response format. This applies for example to PUT requests when pushing Session objects, and PATCH requests to add charging periods to Sessions.
|
link:examples/transport_and_format_version_info_example.json[role=include]
link:examples/transport_and_format_version_details_example.json[role=include]
link:examples/transport_and_format_get_token_example.json[role=include]
link:examples/transport_and_format_get_token_list_example.json[role=include]
When the development of OCPI was started, it was designed for peer-to-peer communication between CPO and eMSP. This has advantages, but also disadvantages. Having to set up and maintain OCPI connections to a lot of parties requires more effort than doing it for only a couple of connections. By communication via one or more Hubs, the amount of OCPI connections is reduced, while still being able to offer roaming to a lot of different parties and customers.
With the introduction of Message Routing, OCPI is now better usable for communication via Hubs.
All examples/sequence diagrams in this section use the roles CPO and eMSP as examples, they could be switched, it could be other roles.
With Message Routing functionality it also becomes possible to support Platforms that host multiple roles.
A lot of parties are not only CPO or eMSP. Most are both CPO and eMSP.
Some parties are doing business in multiple countries, which means to operate with different country_codes
Some parties have a platform on which the host service for other CPOs/eMSPs.
Some parties are themselves CPO and host CPO services for others,
but other parties are (themselves) not a CPO or other role in the EV charging landscape
but do provide service to CPOs/eMSPs, etc.
When OCPI is used to communicate to/from a Platform or via a Hub (which is the most common usage of OCPI, only exception is a peer-to-peer connection between two parties that both have only one OCPI party and role implemented.) the following four HTTP headers are to be added to any request/response to allow messages to be routed.
When implementing OCPI these four headers SHALL be implemented for any request/response to/from a Functional Module. This does not mean they have to be present in all request. There are situation/special request where some headers can or shall be omitted, See: Open Routing Request
Only requests/responses from Function Modules: such as: Tokens, Locations, CDRs etc. SHALL be routed, so need the routing headers.
The requests/responses to/from Configuration Modules: Credentials, Versions and Hub Client Info are not to be routed, and are for Platform-to-Platform or Platform-to-Hub communication. Thus routing headers SHALL NOT be used with these modules.
HTTP Header | Datatype | Description |
---|---|---|
OCPI-to-party-id |
CiString(3) |
'party id' of the connected party this message is to be sent to. |
OCPI-to-country-code |
CiString(2) |
'country code' of the connected party this message is to be sent to. |
OCPI-from-party-id |
CiString(3) |
'party id' of the connected party this message is sent from. |
OCPI-from-country-code |
CiString(2) |
'country code' of the connected party this message is sent from. |
Note
|
HTTP header names are case-insensitive |
For simplicity, connected clients might push (POST, PUT, PATCH) information to all connected clients with an "opposite role", for example: CPO pushing information to all eMSPs and NSPs, eMSP pushing information to all CPOs. (The role "Other" is seen as an eMSP type of role, so Broadcast Push from a CPO is also sent to "Other". Messages from "Other" are only sent to CPOs and not to eMSPs though.)
When using Broadcast Push, the Hub broadcasts received information to all connected clients. To send data through a Hub might be very useful to share information like Locations or Tokens with all parties connected to the Hub that have implemented the corresponding module. This means only one request to the Hub will be necessary, as all connected clients will be served by the Hub.
To send a Broadcast Push, the client uses the party-id and country-code of the Hub in the 'OCPI-to-' headers. The Hub parses the request and sends a response to the client, which optionally contains its own party-id and country-code in the 'OCPI-from-' headers. The Hub then sends the pushed data to any client implementing the corresponding applicable module, using its own party-id and country-code in the 'OCPI-from-' headers. The client receiving a Push from a Hub (with the Hubs information in the 'OCPI-from-' headers) will respond to this Push with the Hubs party-id and country-code in the 'OCPI-to-' headers.
GET SHALL NOT be used in combination with Broadcast Push. If the requesting party wants to GET information of which it does not know the receiving party, an Open Routing Request MUST be used. (see below)
Broadcast Push SHALL only be used with information that is meant to be sent to all other parties. It is useful to share data like Tokens and Locations, but not so much for CDRs and Sessions as these pieces of information are specific to only one party and are possibly even protected by GDPR or other laws.
Note
|
For "Client Owned Objects", the party-id and country-code in the URL segments will still be the original party-id and country-code from the original client sending the Broadcast Push to the Hub. |
When a Hub has the intelligence to route messages based on the content of the request, or the requesting party does not know the destination of a request, the 'OCPI-to-' headers can be omitted in the request towards the Hub. The Hub can then decide to which party a request needs to be routed, or that it needs to be broadcasted if the destination cannot be determined.
This has nothing to do with Broadcast Push though, as Broadcast Push only works for the Push model, not for GET requests.
Open Routing Requests are possible for GET (Not GET ALL), POST, PUT, PATCH and DELETE.
A client (Receiver) can request a GET on the Sender interface of a module implemented by a Hub. To request a GET All from a Hub, the client uses the party-id and country-code of the Hub in the 'OCPI-to-' headers, and calls the GET method on the Sender interface of a module.
The Hub can then combine objects from different connected parties and return them to the client.
The client can determine the owner of the objects by looking at the county_code
and party_id
in the individual objects returned by the hub.
The following section shows which headers are required/optional and which 'OCPI-to-'/'OCPI-from-' IDs need to be used.
This is not an exclusive list, combinations are possible.
This table contains the description of which headers are required to be used for which message when a request is sent directly from one platform provider to another platform provider, without a Hub in between. The headers are addressing the parties to/from which the message is sent, not the platform itself.
Name | Route | TO Headers | FROM Headers |
---|---|---|---|
Direct request |
Requesting platform provider to Receiving platform provider |
Receiving-party |
Requesting-party |
Direct response |
Receiving platform provider to Requesting platform provider |
Requesting-party |
Receiving-party |
This table contains the description of which headers are required to be used for which message when a request is routed from one platform to another platform via a Hub.
Name | Route | TO Headers | FROM Headers |
---|---|---|---|
Direct request |
Requesting platform to Hub |
Receiving-party |
Requesting-party |
Direct request |
Hub to receiving platform |
Receiving-party |
Requesting-party |
Direct response |
Receiving platform to Hub |
Requesting-party |
Receiving-party |
Direct response |
Hub to requesting platform |
Requesting-party |
Receiving-party |
This table contains the description of which headers are required to be used for which message when a request is a Broadcast Push to the Hub.
Name | Route | TO Headers | FROM Headers |
---|---|---|---|
Broadcast request |
Requesting platform to Hub |
Hub |
Requesting-party |
Broadcast response |
Hub to requesting platform |
Requesting-party |
Hub |
Broadcast request |
Hub to receiving platform |
Receiving-party |
Hub |
Broadcast response |
Receiving platform to Hub |
Hub |
Receiving-party |
This table contains the description of which headers are required to be used for which message when the routing of a request needs to be determined by the Hub itself. For an Open Routing Request, the TO headers in the request from the requesting party to the Hub MUST be omitted.
Name | Route | TO Headers | FROM Headers |
---|---|---|---|
Open request |
Requesting platform to Hub |
Requesting-party |
|
Open request |
Hub to receiving platform |
Receiving-party |
Requesting-party |
Open response |
Receiving platform to Hub |
Requesting-party |
Receiving-party |
Open response |
Hub to requesting platform |
Requesting-party |
Receiving-party |
This table contains the description of which headers are required to be used when doing a GET All via a Hub. For a GET All via Hub: The HTTP Method SHALL be GET, The call is to a Senders Interface, the TO headers in the request to the Hub has to be set to the Hub.
Name | Route | TO Headers | FROM Headers |
---|---|---|---|
GET All via Hubs request |
Requesting platform to Hub |
Hub |
Requesting-party |
GET All via Hubs response |
Hub to receiving platform |
Requesting-party |
Hub |
There are rare situation, probably use cases not foreseen by the team developing OCPI,
where a certain field, that is required, cannot be filled. In such cases, and only in such cases,
it is allowed to set a string field to the value: #NA
.
#NA
is not allowed to be used when a party does not have or want to provide the data, but is able to provide the data when they would spend time/resources to get/provide the data.
For debugging issues, OCPI implementations are required to include unique IDs via HTTP headers in every request/response.
HTTP Header | Description |
---|---|
X-Request-ID |
Every request SHALL contain a unique request ID, the response to this request SHALL contain the same ID. |
X-Correlation-ID |
Every request/response SHALL contain a unique correlation ID, every response to this request SHALL contain the same ID. |
Note
|
HTTP header names are case-insensitive |
It is advised to used GUID/UUID as values for X-Request-ID and X-Correlation-ID.
When a Hub forwards a request to a party, the request to this party SHALL contain a new unique value in the X-Request-ID HTTP header, not a copy of the X-Request-ID HTTP header taken from the incoming request that is being forwarded.
When a Hub forwards a request to a party, the request SHALL contain the same X-Correlation-ID HTTP header (with the same value).
As OCPI contains multiple interfaces. Different endpoints are available for messaging. The protocol is designed such that the exact URLs of the endpoints can be defined by each party. It also supports an interface per version.
The locations of all the version-specific endpoints can be retrieved by fetching the API information from the versions endpoint. Each version-specific endpoint will then list the available endpoints for that version. It is strongly recommended to insert the protocol version into the URL.
For example: /ocpi/cpo/2.2.1/locations
and /ocpi/emsp/2.2.1/locations
.
The URLs of the endpoints in this document are descriptive only. The exact URL can be found by fetching the endpoint information from the API info endpoint and looking up the identifier of the endpoint.
Operator interface | Identifier | Example URL |
---|---|---|
Credentials |
credentials |
|
Charging location details |
locations |
|
eMSP interface | Identifier | Example URL |
---|---|---|
Credentials |
credentials |
|
Charging location updates |
locations |
|
During communication over OCPI, one of the communicating parties might be unreachable for an undefined amount of time. OCPI works event-based, new messages and status are pushed from one party to another. When communication is lost, updates cannot be delivered.
OCPI messages SHOULD NOT be queued. When a client does a POST, PUT or PATCH request and that request fails or times out, the client should not queue the message and retry the same message again later.
When the connection is re-established, it is up to the target-server of a connection to GET the current status from to source-server to get back to a synchronized state.
For example:
-
CDRs of the period of communication loss can be retrieved with a GET command on the CDRs module, with filters to retrieve only CDRs of the period since the last CDR has been received.
-
Status of EVSEs (or Locations) can be retrieved by calling a GET on the Locations module.