PILLAR GUIDE · UPDATED 11 AUG 2026

OCPI 2.2.1: the complete guide

A module-level guide to the Open Charge Point Interface — the roles, routing patterns, credentials exchange and data objects that make EV roaming work. Based on the official OCPI 2.2.1-d2 document, and on running it across 30+ partner networks in production.

IN ONE PARAGRAPH

OCPI is how independent charging companies transact with each other. Ten modules, two roles per module, one discovery endpoint, and a strict separation between the live view of a charge and the immutable record you bill from. This page is the map; every module below links to a full guide with worked JSON.

The problem OCPI solves

A driver buys a contract from one company and charges at hardware owned by another. The company that owns the driver relationship — the eMSP, or e-Mobility Service Provider — usually owns no chargers. The company that owns the chargers — the CPO, or Charge Point Operator — usually has no relationship with that driver.

For a charge to happen, those two companies have to agree on five things across a network boundary: does this charger exist and is it free, is this driver allowed to use it, what does it cost, has charging started, and what is owed to whom afterwards. OCPI is the standard answer to all five.

Without it, every eMSP would need a bespoke integration with every CPO. OCPI turns that N×N problem into a single protocol each party implements once.

The roles

OCPI 2.2 introduced multiple roles per connection, and 2.2.1 keeps them. A single party can hold several — a company operating chargers and selling mobility contracts declares both.

RoleWho it isTypically owns
CPOCharge Point OperatorLocations, Sessions, CDRs, Tariffs
EMSPe-Mobility Service ProviderTokens, and the driver relationship
HUBRoaming hub / brokerRouting between many parties — see HubClientInfo
NSPNavigation Service ProviderConsumes Locations for maps and routing
SCSPSmart Charging Service ProviderShapes power via ChargingProfiles
OTHERAnything elseEscape hatch for roles the spec does not name

Identity is the pair country_code + party_id — a two-character country code and a three-character party identifier. party_id is unique only within a country, so treating it as a global key breaks the first time you onboard across a border.

Sender and Receiver

Every module has a direction, and it is not the same direction for every module. The Sender owns the data and is the source of truth. The Receiver consumes it.

This inverts between modules. The CPO is Sender for Locations because it owns the chargers. The eMSP is Sender for Tokens because it owns the customers. Neither party is globally the client or the server — both run an API, and which one you call depends on which module you are using.

Each party publishes its role per module in its version details, which is why the pair (module, role) rather than module alone is the key when you cache a partner's endpoint directory.

The ten modules

Every module below has a full guide with worked JSON, field tables and production lessons.

ModulePurposeSender
VersionsDiscover supported versions and the endpoint directory. The only URL you are given in advance.Both
CredentialsRegister parties and exchange tokens — the Token A/B/C handshake.Both
LocationsPublish sites, EVSEs and connectors, plus live availability status.CPO
SessionsThe live, mutable view of a charge in progress.CPO
CDRsThe immutable final record that billing and settlement run on.CPO
TariffsPricing as composable data — elements, components, restrictions.CPO
TokensWho may charge, by whitelist or real-time authorisation.eMSP
CommandsRemote start, stop, reserve, cancel and unlock.eMSP
ChargingProfilesSmart charging — shaping power across company lines.SCSP / eMSP
HubClientInfoWhich parties on a hub are currently reachable.Hub

The five commands

The Commands module carries five actions, each with its own page:

CommandDoes
START_SESSIONBegin charging at an EVSE for a given token
STOP_SESSIONEnd an in-progress session by session ID
RESERVE_NOWHold an EVSE for a token until an expiry time
CANCEL_RESERVATIONRelease a reservation early
UNLOCK_CONNECTORPhysically release a latched cable

Transport, authentication and routing

OCPI is plain HTTPS with JSON bodies. There is no message broker, no custom framing, and no long-lived socket — that lives one layer down in OCPP.

Authentication

A single header carries a credentials token, base64-encoded:

Authorization: Token <base64-encoded-token>

Forgetting the base64 encoding is the most common cause of a 401 on a brand-new integration. The token itself comes from the Credentials module handshake.

Routing headers

Four headers identify the two ends of a message. They are optional on direct connections and mandatory through a hub, because the hub has no other way to know where to forward:

OCPI-from-country-code: IN
OCPI-from-party-id:     EMS
OCPI-to-country-code:   IN
OCPI-to-party-id:       EFI
X-Request-ID:           7c1a4e90
X-Correlation-ID:       booking-88231

X-Request-ID is unique per HTTP request. X-Correlation-ID stays constant across a logical transaction, including the asynchronous callbacks that commands generate. Populating both is the difference between a resolvable partner dispute and an unresolvable one — when a CPO asks what happened to a session three weeks ago, the correlation ID is the only thing that answers.

Push, pull and pagination

Most data modules support both directions, and a robust integration uses both.

MechanismVerbUse for
Push, full objectPUTCreating or wholly replacing an object
Push, partialPATCHField-level updates — status changes, running kWh
PullGETBaseline sync, backfill, and repairing missed pushes

Push alone is not enough. Pushes get lost, and a lost push is silent — there is no error, just stale data. Scheduled pulls with date_from are what repair the drift, and every production integration needs them.

Pagination

GET on a collection is paginated with offset and limit, plus optional date_from and date_to. The response carries the next page in a Link header alongside X-Total-Count and X-Limit:

GET /ocpi/2.2.1/locations?offset=0&limit=100&date_from=2026-08-10T00:00:00Z

Link: <https://cpo.example.com/ocpi/2.2.1/locations?offset=100&limit=100>; rel="next"
X-Total-Count: 3471
X-Limit: 100

Follow the Link header rather than constructing the next URL yourself — a partner may cap limit below what you requested, and hand-built URLs then silently skip records.

Ordering

Every OCPI object carries last_updated. Network retries mean updates arrive out of order, so reject any update older than what you already hold. Without that guard a delayed “available” overwrites a newer “charging”, and your map lies to drivers.

Error code families

OCPI returns HTTP 200 with a status_code in the body for most application-level outcomes. The families are meaningful and worth handling distinctly:

CodeMeaningWhose problem
1000Success
2000Generic client errorYours
2001Invalid or missing parametersYours
2002Not enough informationYours
2003Unknown locationYours — or your Locations data is stale
2004Unknown tokenYours — token not synced to this CPO
3000Generic server errorTheirs
3001Unable to use the client's APITheirs calling you — usually a firewall
3002Unsupported versionNegotiation — no common version
3003No matching endpointsTheir endpoint directory is incomplete
4000Generic hub errorThe hub
4001Unknown receiverParty not on this hub
4002Timeout on forwarded requestThe far party, via the hub
4003Connection problemThe hub's link to the far party
The 4xxx family is why hubs need different handling. A 4002 means the hub reached the partner and the partner did not answer — retrying immediately hits the same wall. A 4001 means the party is not on that hub at all, which is a configuration problem no retry will fix. Treating all failures identically produces retry storms against parties that were never going to respond.

A complete roaming transaction

This is how the modules fit together. One driver, one charge, from onboarding to money.

  1. Registration — once per partner. The Versions module negotiates a version and returns the endpoint directory; the Credentials module completes the Token A/B/C handshake.
  2. Catalogue sync — continuous. The CPO publishes Locations and Tariffs; the eMSP pushes Tokens. Now each side knows what exists, what it costs, and who may use it.
  3. Discovery. The driver opens an app and sees chargers with live availability and a price — both from the CPO's data.
  4. Authorisation. Either the CPO authorises locally from its whitelist, or it calls the eMSP in real time and receives an authorization_reference.
  5. Start. The eMSP sends START_SESSION. The CPO acknowledges, relays it to the charger over OCPP, and posts the real result to a callback.
  6. Charging. A Session object appears and updates with accumulating energy and an estimated cost.
  7. Stop. The driver unplugs, or STOP_SESSION is sent. The session moves to COMPLETED.
  8. Settlement. The CPO finalises meter values, applies the tariff, and issues one immutable CDR. The eMSP invoices the driver from it and settles with the CPO against it.

The authorization_reference set at step 4 flows through steps 5 to 8, which is what lets you trace a line on an invoice back to the moment a driver was authorised. It is optional in the spec and indispensable in practice.

Version history

VersionYearWhat changed
2.02016First widely circulated release
2.1.12017Long the de facto standard; still live on many networks
2.22019Roaming hubs, multiple roles per party, smart charging, sender/receiver roles on endpoints
2.2.12021Final release with CDR and connector updates, additional types, improved descriptions
2.2.1-d22023Documentation revision

Documentation revisions (d2, d3, d4) do not change message content or add fields. They clarify descriptions and correct documentation issues. A partner advertising 2.2.1-d2 and one advertising plain 2.2.1 are wire-compatible.

2.1.1 remains the practical constraint on many integrations. It has no hub support, one role per connection, and no smart charging. If a partner is on 2.1.1, features built on 2.2+ simply are not available — confirm the version before promising functionality.

Where OCPI ends and OCPP begins

They are different layers and are frequently confused.

OCPIOCPP
BetweenCompany and companyCharger and its own operator
TransportHTTPS request/responsePersistent WebSocket
Crosses a company boundaryYes — that is the pointNo
Owned byEVRoaming FoundationOpen Charge Alliance

A remote start illustrates the split cleanly: the eMSP sends START_SESSION over OCPI to the CPO; the CPO sends RemoteStartTransaction over OCPP to the charger. The eMSP never touches OCPP, and the charger never knows OCPI exists. Full detail in the OCPP guide.

An implementation roadmap

The order matters — each phase depends on the one before it.

PhaseBuildYou can now
1Versions + CredentialsRegister with a partner and discover their endpoints
2Locations + TariffsShow chargers and prices in an app
3TokensLet your drivers authorise at partner chargers
4Sessions + CDRsShow live charges and bill for them — a complete product
5CommandsRemote start and stop from the app
6ChargingProfiles + HubClientInfoSmart charging and hub connectivity

Phases 1 to 4 are a shippable roaming integration. Phase 5 is what drivers notice most. Phase 6 is optional for many operators and, in the case of ChargingProfiles, not supported by every partner regardless.

Production lessons

  • Store only the versions URL. Every other endpoint is a cached derivation you can rebuild. Persisting module URLs as configuration becomes unmaintainable around partner number ten.
  • Never bill from a Session. The CDR is the financial record. This single rule prevents more revenue disputes than any other.
  • Enforce last_updated ordering everywhere. It applies to Locations, Sessions, Tokens and Tariffs alike.
  • Reconcile continuously. Sessions completed without CDRs are unbilled revenue; CDRs without sessions mean offline charging. Both ratios are partner quality metrics.
  • Set authorization_reference on everything that accepts it. It is the only clean thread from authorisation to invoice.
  • Treat NOT_SUPPORTED as a capability flag, not an error. Cache it per partner and stop offering actions that will never work.
  • Log both routing headers on every request. Partner disputes are resolved by correlation IDs or not at all.
  • Audit against the spec, not against what works. An integration that functions with one partner can still be non-conformant, and the second partner is where that surfaces.

Official reference

The normative source is the OCPI 2.2.1-d2 document published by the EVRoaming Foundation. Everything on this page and in the linked module guides is written against it, with the operational detail that comes from running it across partner networks in production rather than from reading it alone.

Frequently asked questions

What is OCPI?

The Open Charge Point Interface, a protocol that lets independent EV charging companies transact with each other. It defines how a charge point operator publishes locations, availability and prices, how an e-mobility provider authorises its drivers at someone else's chargers, and how the resulting session is billed and settled between the two companies.

What is the difference between OCPI and OCPP?

OCPI runs between companies over HTTPS — a CPO and an eMSP exchanging locations, tokens, sessions and CDRs. OCPP runs between a charger and its own operator over a persistent WebSocket. A remote start crosses both: the eMSP sends START_SESSION over OCPI, and the CPO sends RemoteStartTransaction over OCPP.

What are the OCPI modules?

Ten: Versions, Credentials, Locations, Sessions, CDRs, Tariffs, Tokens, Commands, ChargingProfiles and HubClientInfo. Versions and Credentials handle discovery and registration; the rest carry operational and financial data.

What does Sender and Receiver mean in OCPI?

The Sender owns the data and is the source of truth; the Receiver consumes it. The direction inverts between modules — the CPO is Sender for Locations because it owns the chargers, while the eMSP is Sender for Tokens because it owns the customers.

Is OCPI 2.2.1-d2 a different protocol version from 2.2.1?

No. Documentation revisions d2, d3 and d4 clarify descriptions and fix documentation issues without changing message content or adding fields. A party on 2.2.1-d2 is wire-compatible with one on plain 2.2.1.

What are the OCPI error code families?

1000 is success. The 2xxx family is client errors such as invalid parameters or an unknown token. The 3xxx family is server errors including unsupported version and inability to reach the client's API. The 4xxx family is hub errors such as unknown receiver or a timeout on a forwarded request.

What order should you implement OCPI modules in?

Versions and Credentials first, since nothing works without registration. Then Locations and Tariffs to show chargers and prices, then Tokens for authorisation, then Sessions and CDRs to complete billing. Commands, ChargingProfiles and HubClientInfo come last.