OCPI 2.1.1 to 2.2 Migration: The Step-by-Step Order

OCPI 2.1.1 to 2.2 migration in the order to actually do it: what changed, the Credentials handshake to fix first, and how to upgrade without breaking partners.

OCPI 2.2 was not a normal point release. It changed the connection model, added two new modules, restructured authentication, and tightened the data contracts for several existing modules. If you treat it like a routine upgrade from 2.1.1 you will spend the migration debugging mysterious 401 errors and broken CDR transmissions.

This guide is the migration in the order I would actually do it.

Why OCPI 2.2 happened

OCPI 2.1.1 worked, but two things were holding the protocol back. (If you’re new to the protocol itself, start with what OCPI is before the migration mechanics below.)

First, the architecture quietly assumed hubs. Peer-to-peer roaming was technically possible in 2.1.1, but the Credentials flow made it awkward enough that connecting through a hub and letting the hub fan out was the path of least resistance. Hubs solve a real arithmetic problem: direct connections between every CPO and every eMSP grow as the product of the two populations, not their sum. What they also do is put a party in the middle of a protocol that did not yet have the vocabulary to describe one. The trade-off between the two shapes is worked through in hub versus peer-to-peer OCPI.

Second, the protocol’s data model was getting strained. Tariffs needed more structure to express modern pricing: time-of-use rates, parking fees for cars that linger after charging, and distinct tariff variants for the same session. CDRs needed a defined place for cryptographically signed metering data, because some markets require a verifiable meter reading behind every billed kilowatt-hour rather than a number the back office asserts. Germany’s calibration law (Eichrecht) is the best-known example of that requirement. And smart-charging coordination needed a real module instead of glue code.

OCPI 2.2 addressed both, and the two changes behave differently in a project plan. The data-model changes announce themselves as schema diffs, so they get tickets and estimates. The architectural change produces no diff at all, which is exactly why it is worth writing onto the plan by hand.

The headline changes

A short list before the details.

  • Roles became explicit. OCPI 2.2 introduced a role field and abstracted roles from the connection itself. Your party declares which roles it operates, and the set of recognized roles expanded well beyond the CPO/eMSP pair of 2.1.1 to include Roaming Hub, NAP, NSP, SCSP, and others.
  • Hub vs peer-to-peer is a first-class choice. The Credentials handshake works the same way structurally for both; what changes is whether the other side of the handshake is the partner directly or a hub representing many partners.
  • HubClientInfo is a new module that lets hubs publish the list of parties they connect on behalf of, so the other side can discover available partners.
  • ChargingProfiles is a new module for smart charging, letting an eMSP request that a session charge at a specific power profile.
  • CDR signing is now a defined extension via the SignedData object, which matters for German and other regulated markets.
  • Tariffs got richer. A PARKING_TIME dimension lets you price time plugged in without charging, a TariffType enum distinguishes tariff variants (ad-hoc vs RFID vs charging-preference profiles), and restriction logic was clarified.
  • The Credentials object grew to include business_details, roles, and clearer URL conventions. The token-rotation flow is more explicit.

The Credentials handshake (start here)

If you fix nothing else first, fix Credentials. Every other module depends on the handshake working, and the failure mode is silent: you get 401s with no real explanation.

In OCPI 2.1.1, your Credentials object looked roughly like this:

{
  "url": "https://your-server/ocpi/2.1.1/",
  "token": "abc123",
  "party_id": "ABC",
  "country_code": "US",
  "business_details": { "name": "Example CPO" }
}

In OCPI 2.2, the same object has more structure and is role-aware:

{
  "token": "abc123",
  "url": "https://your-server/ocpi/2.2/",
  "roles": [
    {
      "role": "CPO",
      "party_id": "ABC",
      "country_code": "US",
      "business_details": { "name": "Example CPO" }
    }
  ]
}

The roles array is what enables a single endpoint to operate as multiple roles (CPO + eMSP, or a Hub representing many parties). For a single-role implementation this looks like overhead, but the second time you need to add a role you’ll be glad it exists.

Token rotation also became explicit. OCPI 2.2 names three registration tokens: CREDENTIALS_TOKEN_A, CREDENTIALS_TOKEN_B, and CREDENTIALS_TOKEN_C. Take the perspective of the party initiating registration (the Sender). The flow is:

  1. The partner (Receiver) generates token A and gives it to you out-of-band (email, ticket, contract attachment). Token A only authorizes the registration itself.
  2. You POST your real credentials, including token B, to the partner’s /credentials endpoint, authenticated with token A.
  3. The partner stores your token B and returns their token C in the response.
  4. From now on, you call the partner with token C; the partner calls you with token B.
  5. Token A is thrown away and may no longer be used.
sequenceDiagram
    participant You
    participant Partner
    Note over You,Partner: Partner generates token A,<br/>sent to you out-of-band
    You->>Partner: POST /credentials with token B<br/>auth: token A
    Partner-->>You: Response with token C
    Note over You,Partner: token A discarded
    You->>Partner: Calls use token C
    Partner->>You: Calls use token B

OCPI 2.1.1 had the same flow conceptually, but 2.2 names the tokens explicitly and clarifies invalidation rules, which removes a lot of “wait, which token are you using” ambiguity in support tickets.

The Versions endpoint negotiation

GET /ocpi/versions returns the list of versions you support. In 2.2 you simply add 2.2 to the array; your existing 2.1.1 entry stays. Partners pick the highest version both sides support. The mechanics of version discovery and the handshake are covered in depth in our guide to OCPI credentials and versions.

A common mistake during migration: announcing 2.2 before the rest of your implementation is ready. If a partner does the version selection and picks 2.2, they expect every module to behave per the 2.2 spec. Stage the announcement.

Module-by-module changes

Locations

Mostly compatible. The big addition is the EnergyMix object on Location, which lets you describe the energy source (renewable, fossil, mixed, with optional environmental_impact data). Useful for green-certified roaming.

Minor: images is more structured (with width, height, thumbnail). directions clarified. The Connector.terms_and_conditions field is new.

Tariffs

This is where 2.2 changed the most outside of Credentials. The TariffDimensionType enum gained PARKING_TIME (a per-hour charge for being plugged in but not drawing energy) alongside the existing ENERGY, TIME, and FLAT. (Reservation time is priced through the CDR side of the protocol via the RESERVATION_TIME charging-period dimension and the total_reservation_cost field, not through a tariff dimension of the same name; it is a common point of confusion, so check the spec before you model it.)

The new TariffType enum lets you mark a tariff as REGULAR (RFID, no charging preference), AD_HOC_PAYMENT (drive-by payment), PROFILE_CHEAP, PROFILE_FAST, or PROFILE_GREEN. That is what lets an eMSP pick between tariff variants for the same session instead of guessing from the price alone. Tariff restriction logic (day-of-week, time-of-day, energy and power bands) was also clarified.

If your billing logic was hard-coded around 2.1.1’s dimensions, you’ll touch the most code here. For a field-by-field walk through the module itself, see the OCPI Tariffs module deep-dive.

Sessions

Stable. The biggest practical change is that the lifecycle of intermediate updates is clearer: when you must send an interim Session update, and which fields are mandatory in each state. Read the state-machine table against your own sender code rather than against your 2.1.1 behaviour. Under-sending interim updates does not fail loudly; it surfaces later as an eMSP whose session view goes stale halfway through a charge.

CDRs

Two things matter.

The SignedData object is the defined way to attach cryptographically signed metering data to a CDR or to an individual charging period. Whether you need it is a jurisdictional question rather than a technical one, so the check is specific: ask what the metrology or weights-and-measures authority in each country you operate in requires of a public charging invoice, and whether a signed meter reading is part of that. Germany’s calibration law (Eichrecht) is the best-known driver of the field’s existence. If none of your markets require it today, defer the implementation, but leave the signed blob somewhere to live in your data model.

The CDR totals were extended to match the richer tariff model. total_parking_time breaks out the portion of a session where the EV was plugged in but not charging, and cost totals like total_time_cost, total_parking_cost, and total_reservation_cost let you itemize a bill by dimension. If you charge parking or reservation fees, your CDR has to break them out so the eMSP can reconcile the invoice line by line. The CDRs module deep-dive walks the object field by field.

Tokens

Authentication-method enum extended. Token whitelisting clarified. Behavior of AD_HOC_USER and APP_USER distinguished. Otherwise compatible.

Commands

Stable. RemoteStart, RemoteStop, ReserveNow, CancelReservation, and UnlockConnector all work the same way, and the async response pattern is unchanged.

Credentials (already covered above)

HubClientInfo (new in 2.2)

If you’re a hub, you publish a list of the parties you connect on behalf of, so other partners can see who’s reachable through you. If you’re a CPO or eMSP working with a hub, you may not implement this at all.

We have a separate deep-dive on HubClientInfo.

ChargingProfiles (new in 2.2)

Smart-charging instruction module: the eMSP can request that an ongoing session charge to a specific power profile (max amps, schedule, and so on). It is optional in the spec, so treat a given partner’s support for it as something you confirm during onboarding rather than something you assume. Note also that ChargingProfiles only carries the roaming-side request. Execution happens on the charger over OCPP, and any vehicle-side negotiation over ISO 15118, a division of labour set out in how the three protocols fit together. Detailed in our ChargingProfiles guide.

Migration order (CPO perspective)

If you’re operating a CPO and rolling out 2.2, this is the order I would use.

  1. Engineer the new Credentials handshake with token A/B/C and roles. Don’t expose it yet.
  2. Add 2.2 endpoints for the modules that didn’t change much: Locations, Sessions, Tokens, Commands. These are safe wins.
  3. Migrate Tariffs to the new dimensions and types. Test against your own billing engine to confirm nothing changes for existing tariffs (parity test).
  4. Migrate CDRs to include the new optional fields where applicable.
  5. Announce 2.2 on your Versions endpoint to one trusted partner first. Don’t announce broadly until you’ve completed at least one real partner integration.
  6. Roll out to additional partners one by one. Assume each one surfaces at least one integration-specific bug, and don’t promise a “big bang” cut-over date.
  7. Plan deprecation of 2.1.1, but don’t rush. The date that matters is the one on which no partner still negotiates 2.1.1 against your Versions endpoint, so log what version each partner selects and let that traffic tell you when dropping it is free.

Migration order (eMSP perspective)

For an eMSP the flow is slightly different.

  1. Engineer the new Credentials handshake (same).
  2. Add the ChargingProfiles client side if you intend to use smart charging. Otherwise skip.
  3. Update your tariff consumer to handle the new dimensions and types. A partner CPO can publish a parking or reservation dimension even if you never price one yourself, and an unrecognised dimension is a mis-priced session.
  4. Update your CDR ingestion to handle new optional fields and SignedData if you operate in a regulated market.
  5. Negotiate 2.2 upgrade with each CPO partner.

Common pitfalls

A list of things that have broken real migrations.

  • Token type confusion during the handshake. If your 2.1.1 code calls partners with what it thinks is “the token” but 2.2 expects token C specifically, you get 401s. Be explicit in your code about which token type you’re using.
  • Hard-coded tariff dimensions. If your billing pipeline only knows about ENERGY, TIME, and FLAT, the new dimensions just disappear silently into your DB. Catch them at ingestion or you’ll have under-billed sessions.
  • Versions endpoint advertising 2.2 too early. As noted above, stage the announcement.
  • Different country_code / party_id formats across partners. OCPI is strict: ISO 3166 alpha-2 country code, three-character party ID, uppercase. Validate on the way in as well as on the way out, and never silently normalise a partner’s value, because a lower-cased party ID you quietly upper-case will stop matching the identifier they filter on.
  • Forgetting Locations.publish. The publish boolean on Location was clarified: an unpublished Location should not be sent. Apply the filter in the query that builds the payload, not in the serialiser, or the next endpoint someone adds will leak private sites without anyone noticing.

More failure modes, with the symptoms they produce, are collected in common OCPI integration pitfalls.

Testing

You need partner-style fixtures, not just your own. Tools and approaches:

  • Mock partner: stand up a fake CPO and fake eMSP that you control, exchange Credentials, and round-trip every module.
  • Schema validation: run every outgoing payload through an OCPI JSON Schema in CI, so a malformed object fails your build instead of your partner’s parser. OCPI testing tools covers what is available.
  • Pre-prod with one real partner: there is no substitute for one cooperative partner who is also willing to be on the 2.2 beta with you.
  • Production smoke tests: after each partner goes live on 2.2, do one real session and one real CDR end-to-end before declaring it done.

When NOT to migrate

If your business depends on one partner and that partner is on 2.1.1 indefinitely, you don’t need to move. OCPI 2.1.1 still works.

You should move when:

  • A meaningful number of your partners have moved or are moving.
  • You need a 2.2-only feature (ChargingProfiles for smart charging, signed CDRs for compliance, hub-via-HubClientInfo for discovery).
  • You are rewriting the integration layer anyway. Targeting 2.2 while you are already inside that code costs far less than a dedicated migration project later.

A pragmatic baseline: OCPI 2.3.0 was published in February 2025, and every release assumes the one before it. Treat 2.2 and its 2.2.1 clarifications as the floor you build from rather than a rung you can skip.

What about 2.2.1 and 2.3?

OCPI 2.2 was published in March 2020 and OCPI 2.2.1 in June 2020. They are distinct releases, and you will still meet implementations that speak plain 2.2, so the version string a partner advertises is worth reading precisely. 2.2.1 is a clarification release rather than a feature release: implement 2.2 correctly and you already satisfy most of it. The field-level differences are laid out in OCPI 2.2 versus 2.2.1, and the full release timeline in the OCPI version history.

Aim the migration at 2.2.1 rather than at 2.2. It is the current mainstream release, and the delta from 2.2 is small enough that hitting it while you are already in the code costs close to nothing.

OCPI 2.3.0 was published in February 2025 and is a larger evolution than 2.2.1. We cover it separately in what is new in OCPI 2.3, and any field-level detail is worth confirming against the official OCPI repository before you plan against it. The sequencing advice does not change: get 2.2.1 right first.

The honest summary

OCPI 2.2 is worth doing, and the reason is not really the feature list. What changed is where the ambiguity lives. In 2.1.1, the token flow, the position of a hub, and the shape of a tariff were settled partly by convention between two specific parties; 2.2 wrote those conventions into the spec. The practical effect is that a whole class of bug moves from “email the partner and find out what they meant” to “read the document”.

Which is also the trap, and it is worth being blunt about it. The instinct during migration is to validate the new implementation against the old one, because the old one demonstrably works in production. But your 2.1.1 code encodes exactly the private conventions 2.2 replaced, so every place it agrees with itself is a place you have not tested. Validate against the spec, start with Credentials, and stage the Versions announcement until one real partner has run a real session end to end.

Quick check

Q1. Why is the Credentials handshake the recommended starting point for a 2.2 migration?
Q2. How do two OCPI parties agree on which version to speak?
Q3. Which TariffDimensionType did OCPI 2.2 add for time spent plugged in but not charging?
Q4. What is the safest way to announce 2.2 support to partners?
Q5. How is OCPI 2.2.1 best described relative to 2.2?

Frequently asked questions

Can I support OCPI 2.1.1 and 2.2 at the same time?

Yes. OCPI is designed for this. The Versions endpoint lets partners discover which versions you speak, and you negotiate the highest common version per partner. Running both is how a migration is meant to work: 2.1.1 keeps serving existing traffic while 2.2 is proven partner by partner, and you retire 2.1.1 once no partner still negotiates it.

Do I have to use a hub in OCPI 2.2?

No. Peer-to-peer was always allowed, but OCPI 2.2 made it a first-class architecture choice. You can connect directly to partners, go through a hub, or mix both. The Credentials module handles either pattern.

What is the single biggest behavior change to watch for?

The Credentials handshake. The token rotation flow and the structure of the credentials object both changed, and the new role-based design means you need to declare your roles (CPO, eMSP, Hub) explicitly. A successful handshake is the precondition for everything else.

How long does a real OCPI 2.2 migration take?

There is no fixed number, because the schedule is not set by your own code. The engineering is bounded by the spec and you can estimate it from the module diff. The rollout is bounded by how quickly each partner can give you a joint testing window, so count your partners, ask each one for a date, and the sum of those windows is your real timeline.

Found this useful? Share it.