PHP2Shell alert: critical PHP flaws CVE-2026-109618 / 109619 — check your servers and get patched fast
HashTech Systems
EV Charging
Part 9 of 10

OCPI Charging Profiles and Hub Client Info: Smart Charging and Hubs

The OCPI 2.2.1 ChargingProfiles module for SCSPs, its async result flow, and the HubClientInfo module plus practical notes on working with hubs.

HashTech Engineering · October 26, 2026 · 5 min read

OCPI 2.2 added two modules that 2.1.1 lacked. Charging Profiles lets a Smart Charging Service Provider (SCSP), or an eMSP acting as one, ask a CPO to limit the power of a specific session. Hub Client Info lets a roaming hub tell its connected parties who else is reachable. They are less commonly implemented than locations or CDRs, but both matter as networks scale: one for grid and price-aware charging, the other for operating behind a hub.

This is part 9 of the OCPI 2.2.1 series. For the underlying smart charging concepts, see smart charging and load management and the OCPP view in OCPP 1.6 smart charging.

Charging Profiles: who does what

The CPO implements the Receiver interface, because it controls the chargers. The SCSP implements the Sender interface, which, as with commands, mainly receives asynchronous results and unsolicited updates. Profiles are always addressed per session, not per EVSE or location: OCPI does not let an outside party set site-wide limits. The CPO keeps responsibility for its own site constraints and combines the SCSP's request with them.

The ChargingProfile object

json
{
  "start_date_time": "2026-10-26T18:00:00Z",
  "duration": 7200,
  "charging_rate_unit": "W",
  "min_charging_rate": 1380,
  "charging_profile_period": [
    { "start_period": 0, "limit": 11000 },
    { "start_period": 3600, "limit": 3700 }
  ]
}
  • charging_rate_unit is W or A. Ampere limits are per phase, as in OCPP.
  • charging_profile_period entries start at start_period seconds after the profile start and run until the next entry.
  • duration (seconds) and start_date_time are optional; without them the profile applies from now, indefinitely for the session.
  • This is deliberately close to the OCPP ChargingSchedule, so the CPO can translate it directly into a TxProfile for the session's transaction.

Setting a profile

http
PUT /ocpi/cpo/2.2.1/chargingprofiles/SESS-000123 HTTP/1.1
Host: cpo.example.com
Authorization: Token OWU4MGE1OWMtNmEyZi00YjVkLThlMWMtMmYzYTRiNWM2ZDdl
Content-Type: application/json
X-Request-ID: 1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d
X-Correlation-ID: 1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d
json
{
  "charging_profile": {
    "charging_rate_unit": "W",
    "charging_profile_period": [
      { "start_period": 0, "limit": 7400 }
    ]
  },
  "response_url": "https://scsp.example.com/ocpi/2.2.1/chargingprofiles/results/set-55f1"
}

Like commands, the CPO answers synchronously with a ChargingProfileResponse and later posts the actual outcome to response_url:

json
{
  "data": { "result": "ACCEPTED", "timeout": 30 },
  "status_code": 1000,
  "timestamp": "2026-10-26T18:00:01Z"
}
OperationSynchronous ChargingProfileResponseTypeAsync result object
PUT {url}/{session_id} set profileACCEPTED, NOT_SUPPORTED, REJECTED, TOO_OFTEN, UNKNOWN_SESSIONChargingProfileResult
GET {url}/{session_id}?duration=...&response_url=... get active profilesameActiveChargingProfileResult (includes the ActiveChargingProfile)
DELETE {url}/{session_id}?response_url=... clear profilesameClearProfileResult

The async result objects carry a result of type ChargingProfileResultType: ACCEPTED, REJECTED or UNKNOWN. TOO_OFTEN deserves attention: chargers and CPO back-ends cannot process a new profile every few seconds, and a CPO should protect itself rather than queue an unbounded stream of updates to a charger over a cellular link.

The active profile

A GET asks the CPO for the profile actually in effect for the next duration seconds. The answer, an ActiveChargingProfile, is the combination of everything that limits the session: the SCSP's request, the CPO's own site limits, and the vehicle. It is typically derived from OCPP GetCompositeSchedule. The CPO can also push changes to it unsolicited, with PUT on the SCSP's Receiver at {url}/{session_id}, for example when local load balancing lowers the limit.

Practical mapping to OCPP

  • The session must have an OCPP transaction; set the profile as a TxProfile bound to that transactionId.
  • Choose a stackLevel that sits correctly relative to the CPO's own TxDefaultProfile and site-level profiles, so site safety limits still win.
  • Convert units if the charger only supports one chargingRateUnit (many AC chargers prefer A). Converting W to A needs the number of phases and the nominal voltage.
  • When the session ends, the profile goes with it. There is nothing to clear afterwards.

The OCPP side of these operations is covered in OCPP 1.6 smart charging and OCPP 2.0.1 smart charging.

Hub Client Info

When you connect to a hub, you have one OCPI connection but many potential counterparties. The hubclientinfo module tells you which ones exist and whether they are currently reachable. The object is small:

json
{
  "party_id": "EXA",
  "country_code": "NL",
  "role": "EMSP",
  "status": "CONNECTED",
  "last_updated": "2026-10-26T07:30:00Z"
}
ConnectionStatusMeaning
CONNECTEDParty is connected to the hub and reachable.
OFFLINEParty is connected but currently unreachable; requests to it will fail or time out at the hub.
PLANNEDConnection is planned, not yet live.
SUSPENDEDConnection is suspended, typically for commercial or contractual reasons.

The hub pushes these with PUT {url}/{country_code}/{party_id} to the party's Receiver interface, and parties can pull the full list from the hub's Sender interface with GET and the usual pagination. Use it to decide whether to push to a party at all, and to show correct partner status in your own operations tooling.

Working with hubs in practice

  • Routing headers are mandatory through hubs. Every request needs OCPI-from-country-code, OCPI-from-party-id, OCPI-to-country-code and OCPI-to-party-id (see Part 3). Missing or wrong headers are rejected or, worse, routed to the wrong party.
  • 4xxx status codes. 4001 unknown receiver, 4002 timeout on the forwarded request, 4003 connection problem. These come from the hub, not your partner; retry 4002 and 4003, and fix configuration on 4001.
  • Broadcast vs addressed messages. Some data, such as locations and tariffs, is usually meant for every eMSP on the hub; sessions and CDRs are always for one specific eMSP. How a hub handles broadcast pushes differs between hubs; read its integration guide rather than assuming.
  • Hubs validate. Hubs commonly enforce stricter validation than the spec (field lengths, mandatory evse_id, coordinate formats). Test against the hub's own test environment, not only against a peer.
  • Timeouts multiply. A real-time authorization through a hub involves two HTTP hops plus the eMSP's processing. Budget for it, as discussed in Part 5.
  • Mixed topologies. It is normal to reach some partners via a hub and others directly. Keep routing decisions per partner and make sure you never deliver the same CDR via both paths.

The commercial side of hub vs peer-to-peer roaming is covered in EV roaming explained.

Key takeaways

  • Charging Profiles are per session, sent by an SCSP to the CPO's Receiver, with synchronous ChargingProfileResponse and asynchronous results to response_url.
  • The CPO combines the request with its own constraints; the ActiveChargingProfile is what is actually applied.
  • Hub Client Info tells you which parties a hub can reach and their ConnectionStatus.
  • Through a hub, routing headers are mandatory and 4xxx codes tell you where the failure was.

Next: Part 10, mapping OCPP to OCPI, where all the modules come together inside a CSMS.

Let's Talk

Ready to build something that lasts?

Whether it's a charging network, an AI rollout, or a core enterprise system — let's talk about what you're building.