Lifecycle and status
A Session is created when charging is authorised and updated as things change. Its status drives what the driver sees.
| Status | Meaning | What the app should show |
|---|---|---|
ACTIVE | Session running — energy may or may not be flowing | Live progress |
COMPLETED | Finished normally; a CDR should follow | Summary, cost pending |
INVALID | The CPO has voided this session | Do not bill; investigate |
PENDING | Created but not yet started | Starting… |
RESERVATION | A reservation, not yet a charge | Reserved |
ACTIVE does not mean current is flowing. A session stays ACTIVE while a fully charged car sits plugged in drawing nothing. If your UI equates ACTIVE with “charging”, drivers will see a progress animation that never advances. Compare kwh across successive updates to know whether energy is actually moving.Worked example: a live session update
PATCH /ocpi/2.2.1/sessions/IN/EMS/sess-77c1
Authorization: Token <base64 token>
OCPI-from-country-code: IN
OCPI-from-party-id: EFI
OCPI-to-country-code: IN
OCPI-to-party-id: EMS
{
"kwh": 18.400,
"total_cost": { "excl_vat": 340.40, "incl_vat": 401.67 },
"last_updated": "2026-08-11T09:47:10Z"
}And the full object it is patching:
{
"country_code": "IN", "party_id": "EFI",
"id": "sess-77c1",
"start_date_time": "2026-08-11T09:12:00Z",
"kwh": 18.400,
"cdr_token": {
"country_code": "IN", "party_id": "EMS",
"uid": "DR-88231", "type": "APP_USER",
"contract_id": "IN-EMS-C0142"
},
"auth_method": "COMMAND",
"authorization_reference": "auth-4471",
"location_id": "LOC-ND-014",
"evse_uid": "EVSE-014-02",
"connector_id": "1",
"currency": "INR",
"total_cost": { "excl_vat": 340.40, "incl_vat": 401.67 },
"status": "ACTIVE",
"last_updated": "2026-08-11T09:47:10Z"
}auth_method tells you how the session was authorised — COMMAND for a remote start you initiated, WHITELIST for an offline-authorised RFID tap, AUTH_REQUEST for a real-time authorisation call. It is how you distinguish a session your app started from one the same driver started by tapping a card at the charger.
The running cost is an estimate
total_cost on a Session is the CPO's best current calculation. It is genuinely useful — drivers want to see the meter running — and it is genuinely not the invoice. It can and does change:
- Time-based components accrue after the update was sent.
- A tariff with time-of-use pricing crosses a boundary mid-session.
- Parking or idle fees begin once charging completes.
- The CPO corrects meter values during CDR finalisation.
Label it. “Estimated” next to a live figure costs nothing and prevents the support ticket that begins “the app said 340 and you charged me 401”.
Push, pull, and staying honest
The CPO is the Sender. Updates arrive by PUT for a full object or PATCH for changed fields, and you can GET with pagination and date_from to reconcile.
Update frequency is not specified by OCPI and varies enormously — some CPOs push every 30 seconds, some every few minutes, some only on state change. Do not build UI that assumes a cadence. Show the last-updated time and let the driver see the data is a minute old rather than pretending it is live.
Two failure modes to design around:
- Sessions that never complete. A charger that goes offline mid-session leaves an
ACTIVEsession that never transitions. Age them out on your side and reconcile against CDRs, or your “currently charging” count drifts upward forever. - Sessions you never saw start. A CDR can arrive for a session that was never pushed, typically because the charger was offline throughout. Your CDR ingestion must not assume a matching session exists.
Reconciling sessions against CDRs
Every completed session should produce exactly one CDR. Tracking that relationship is one of the highest-value pieces of instrumentation you can build:
- Sessions completed without a CDR — after a reasonable window, this is unbilled revenue. Chase it.
- CDRs with no prior session — offline charging. Expected in small volumes, a problem in large ones.
- Energy mismatch between session and CDR — a small delta is normal from final meter reconciliation; a consistent one indicates a metering or tariff issue worth raising with the partner.
The ratio of these three, per partner, is a more honest measure of integration quality than any uptime number.
Production lessons
- Never bill from a Session. The CDR is the financial record. This is the one rule in the module that costs real money to break.
- Always label live cost as estimated.
- Do not equate
ACTIVEwith charging. Comparekwhbetween updates. - Enforce
last_updatedordering. Same rule as Locations — reject stale updates. - Age out stalled sessions rather than letting them accumulate.
- Store
authorization_reference. It is the join key from your original command through to the CDR.
Frequently asked questions
A Session is the mutable, live view of a charge in progress, updated as energy accumulates, with a running cost estimate. A CDR is the single immutable record issued after completion, carrying the binding tariff snapshot and the totals used for billing and settlement. Never bill from a Session.
No. A session remains ACTIVE while a fully charged vehicle sits plugged in drawing no power. To know whether energy is actually flowing, compare the kwh value across successive session updates.
No, it is an estimate. It can change as time-based components accrue, as time-of-use tariffs cross a boundary, as idle fees begin, or when the CPO corrects meter values during CDR finalisation. Always label it as estimated in the UI.
OCPI does not specify a cadence and it varies widely between CPOs — some push every 30 seconds, others only on state change. Do not build UI that assumes a frequency; display the last-updated time instead.
Offline charging. If the charger lost connectivity for the whole session, the CPO may never have pushed a Session object but will still issue a CDR once the charger reconnects. CDR ingestion must not assume a session exists.