Skip to content

OCPI 2.3 Onboarding Guide

The practical hookup guide for a partner CPMS. You operate charge points behind your own CPMS with an OCPI 2.3 interface; this document is what you need to connect it to our platform.

Status honesty, up front. Our OCPI surface is live on 2.2 with 2.3 endpoints mounted, and the genuine 2.3 payload semantics (see Version negotiation) are being rolled out this week. Everything marked [rolling out] below describes the contract as it will be at demo time; everything unmarked is running today. Ask us to re-confirm any [rolling out] item live — we'd rather show you than promise you.

Two OCPI planes, one platform

You will talk to two base URLs (concrete hostnames and sandbox credentials come with your onboarding pack; placeholders used below):

Plane Placeholder What lives there
CPMS roaming surface (CPO + eMSP roles) {CPMS_BASE} versions, credentials, locations, sessions, CDRs, tariffs, tokens, commands
Booking service (BSP) {BOOKING_BASE} the OCPI 2.3 booking module

Both are multi-tenant: each CPO tenant on our platform has its own OCPI country_code + party_id, and your registration establishes a peer relationship per tenant. You only ever see the locations, sessions and bookings of the tenants you are peered with.

Registration

1. Exchange Token A

Out of band (during onboarding), we exchange initial credentials tokens — you receive a Token A authorizing your one registration call against us, and give us one for the reverse direction.

2. Pull versions

GET {CPMS_BASE}/ocpi/versions
Authorization: Token {TOKEN_A}

returns the mutually supported versions:

{
  "data": [
    { "version": "2.2", "url": "{CPMS_BASE}/ocpi/2.2" },
    { "version": "2.3", "url": "{CPMS_BASE}/ocpi/2.3" }
  ],
  "status_code": 1000,
  "timestamp": "2026-08-04T12:00:00Z"
}

GET {CPMS_BASE}/ocpi/2.3 returns the endpoint list for that version (module identifier, role, URL) — always discover module URLs from there rather than hardcoding paths.

3. Credentials handshake (Token A → B → C)

Standard OCPI registration against the shared credentials endpoint:

POST {CPMS_BASE}/ocpi/2.2/credentials
Authorization: Token {TOKEN_A}

with your credentials object — your token for us to call you (Token B), your versions URL, and your roles (country_code, party_id, business details). We validate, fetch your versions, store the peer, and respond with our credentials object containing Token C — the token you use on every subsequent call to us. Token A is then dead.

PUT re-issues (token rotation), DELETE unregisters. The credentials endpoint is shared between 2.2 and 2.3 registrations.

4. eMSP-side registration [rolling out]

For flows where we consume your CPO surface (our drivers roaming to your chargers), our eMSP receiver — /ocpi/emsp/versions + a credentials receiver, so your CPMS can onboard us with the same handshake in the other direction — is rolling out this week, replacing an out-of-band registration path. Token push for driver authorization runs over the standard tokens module once peered.

Module set

CPO side (you consume, at {CPMS_BASE})

Module Role Endpoints Notes
locations SENDER GET /locations, /{id}, /{id}/{evse_uid} site topology incl. HDV bays
sessions SENDER GET /sessions, per-id GET session objects for your drivers
cdrs SENDER GET /cdrs, per-id GET CDR delivery [rolling out] — wired to the signed-CDR generator
tariffs SENDER GET /tariffs, /{tariff_id} reservation-fee tariff objects [rolling out]
tokens RECEIVER GET /tokens/{uid}, POST /tokens/{uid}/authorize full AuthorizationInfo response [rolling out] (today a minimal allowed-stub)
commands RECEIVER POST /commands/{START_SESSION\|STOP_SESSION\|RESERVE_NOW\|CANCEL_RESERVATION} the seam the booking layer drives on partner CPMSs
bookings RECEIVER at {BOOKING_BASE} — see below the booking module lives on the booking service

eMSP side (we consume yours)

Versions + credentials receiver [rolling out], then: your locations (we sync your site topology into the schedule), your commands receiver (we send RESERVE_NOW/START_SESSION near arrival), your sessions/cdrs senders (we track charging progress against bookings).

Authentication

Every OCPI call carries the standard header:

Authorization: Token {TOKEN_C}

Unknown or missing token → HTTP 401 with OCPI status 2001. Peer scoping is strict: a token only reaches the resources of its own peer relationship — a booking created by another EMP is a 2003 Not Found to you, not a 403.

Version negotiation

  • 2.3 preferred, 2.2 legacy. Register on 2.3 if your stack supports it; 2.2 peers stay fully supported — no forced migration.
  • Today the mounted 2.3 endpoints serve 2.2-shaped payloads (route-level aliasing). [rolling out] Genuine OCPI 2.3.0 semantics ship this week on typed 2.3 modules, including the deltas that actually matter on the wire:
  • Price = {before_taxes, taxes[]} (2.2.1's {excl_vat, incl_vat} stays on the 2.2 plane), flowing through Session.total_cost and all CDR total_* fields;
  • Tariff.tax_included (required in 2.3.0) and min_price/max_price as PriceLimit;
  • CDR.booking_id linking a CDR to its booking;
  • Location.time_zone required; Parking objects and EVSE parking links for HDV bays;
  • OCPI DateTime leniency on input (designator-less UTC accepted), canonical …Z UTC output.
  • The two version planes never mix types: what you negotiate is what you parse.

The booking module

The booking module is served by the booking service at {BOOKING_BASE}, authenticated with the same Authorization: Token scheme (booking-plane peer tokens are provisioned by us during onboarding today; discovery via a booking-plane versions endpoint is planned).

POST   {BOOKING_BASE}/ocpi/cpo/2.3.0/bookings
GET    {BOOKING_BASE}/ocpi/cpo/2.3.0/bookings/{booking_id}
DELETE {BOOKING_BASE}/ocpi/cpo/2.3.0/bookings/{booking_id}

Create

{
  "booking_id": "emp-000123",
  "location_id": "LOC-HAM-01",
  "evse_uid": "",
  "start_date_time": "2026-08-14T18:00:00Z",
  "end_date_time": "2026-08-14T19:00:00Z",
  "token": { "uid": "DE-XYZ-C00112233", "type": "RFID" },
  "vehicle_type": "TRUCK_40T",
  "vehicle_dimensions": {
    "height_cm": 400, "length_cm": 1650, "width_cm": 255, "weight_kg": 40000
  }
}
  • booking_id is yours — you mint it, we key on it. Retrying a create with the same id is safe.
  • evse_uid is optional: omit it and the scheduler places the vehicle (the concrete charge point is bound near arrival — for the driver the exact bay matters at the gate, not at booking).
  • vehicle_type / vehicle_dimensions are HDV extensions — send dimensions when you have them and bay compatibility (height, length, weight, access side, charging technology) is checked at admission.
  • Timestamps must be RFC 3339 with timezone designator (…Z recommended).

Response (OCPI envelope):

{
  "data": {
    "booking_id": "emp-000123",
    "location_id": "LOC-HAM-01",
    "evse_uid": "",
    "start_date_time": "2026-08-14T18:00:00Z",
    "end_date_time": "2026-08-14T19:00:00Z",
    "status": "CONFIRMED",
    "last_updated": "2026-08-04T12:00:01Z"
  },
  "status_code": 1000,
  "status_message": "Success",
  "timestamp": "2026-08-04T12:00:01Z"
}

Admission is synchronous: you get CONFIRMED (schedulable) or REJECTED (no feasible slot) in the create response. The creation response additionally carries the driver's offline site-entry credential (PIN/QR) — it is served exactly once and never again on reads. [rolling out] total_cost on the booking response, echoing the applicable reservation fee from the tenant's published tariff.

Statuses

Status Meaning
REQUESTED received, admission in progress
CONFIRMED slot guaranteed
ATTENDING vehicle arrived on site (gate event)
COMPLETED session done, booking fulfilled
CANCELLED cancelled by you (DELETE) or operator
EXPIRED lapsed without effect
NO_SHOW grace period passed without arrival — slot released, no-show fee applies
REJECTED admission refused — no feasible slot

Always uppercase. GET returns the current state; DELETE cancels (inside the free-cancellation window, at no charge) and returns the booking with status CANCELLED.

Errors

OCPI envelope with status_code 2001 (invalid request / auth) or 2003 (unknown booking), alongside the matching HTTP status. Bookings belonging to another peer are indistinguishable from nonexistent ones.

First 30 minutes (sandbox checklist)

  1. Exchange Token A — you'll have it from the onboarding pack, along with {CPMS_BASE}, {BOOKING_BASE} and a booking-plane token.
  2. Register: GET {CPMS_BASE}/ocpi/versionsPOST {CPMS_BASE}/ocpi/2.2/credentials with your Token B → store the Token C we return.
  3. Pull the endpoint list: GET {CPMS_BASE}/ocpi/2.3 — confirm the module URLs you'll use.
  4. GET locations: GET {CPMS_BASE}/ocpi/cpo/2.3/locations — you should see the sandbox HDV site with its bays/EVSEs.
  5. Create a test booking: POST {BOOKING_BASE}/ocpi/cpo/2.3.0/bookings for tomorrow, no evse_uid, with truck dimensions → expect CONFIRMED plus the entry credential.
  6. Read it back: GET …/bookings/{your-booking-id}CONFIRMED (and note the credential is absent on reads — by design).
  7. Cancel it: DELETE …/bookings/{your-booking-id}CANCELLED.

That's the whole loop. From there, point your CPMS's commands receiver at us and we can run the arrival flow (reserve → session → CDR) against your sandbox chargers.


Maintainer footnote (internal — not part of the partner narrative): booking receiver contract internal/ocpi/inbound/receiver.go + ev-suite internal/ocpi23 (ev-suite ADR-141 — mirror, booking-core owns the contract); statuses ADR-0007; credential-once + offline verification ADR-0019/0020; peer scoping ADR-0028-0031 context; total_cost echo + tariffs publication ADR-0032 (proposed); CPO gateway realities & [rolling out] items: ev-suite plan/17 C1/C2 (typed 2.3, CDR wiring D8, AuthorizationInfo, eMSP versions/credentials receiver, token push) — verify merge state before the demo; per-tenant party IDs: tenants.cpo_party_id / ev-suite organizations.party_id.