OCPI Sessions Module: Live Charging Session Tracking, Explained

How CPOs stream live session data to eMSPs via OCPI — when sessions start, how kW is flowing, and how status changes get pushed in real time.

If Locations is the map of available stations and Tariffs is the price list, Sessions is the live action. It’s how a CPO tells an eMSP, in near-real-time, “your customer just started charging, they’re at 47 kW, they’ve delivered 12 kWh so far, the EVSE is now at 60% SOC.”

This is the data that drives the “currently charging” view in an eMSP’s app.

The Session object — what’s inside

A Session represents one charging transaction (one driver, one station, one start/stop). The structure:

{
  "country_code": "NL",
  "party_id": "ABC",
  "id": "SESSION_12345",
  "start_date_time": "2026-10-01T10:30:00Z",
  "end_date_time": null,            // null while ongoing
  "kwh": 12.4,                       // running total
  "cdr_token": {                     // the customer's token (all 5 fields required)
    "country_code": "NL",
    "party_id": "ABC",
    "uid": "12345678905880",
    "type": "RFID",
    "contract_id": "NL-ABC-C12345678-X"
  },
  "auth_method": "AUTH_REQUEST",     // how authorization happened
  "authorization_reference": "AUTH_REF_7f3c21",
  "location_id": "LOC1",
  "evse_uid": "EVSE1",
  "connector_id": "CONN1",
  "meter_id": null,
  "currency": "EUR",
  "charging_periods": [              // detailed metering data
    {
      "start_date_time": "2026-10-01T10:30:00Z",
      "dimensions": [
        {"type": "ENERGY", "volume": 5.2},
        {"type": "TIME", "volume": 0.083}
      ],
      "tariff_id": "TARIFF1"
    }
  ],
  "total_cost": {                    // current estimated cost (with VAT breakdown)
    "excl_vat": 4.97,
    "incl_vat": 5.97
  },
  "status": "ACTIVE",                // ACTIVE, COMPLETED, INVALID, PENDING, RESERVATION
  "last_updated": "2026-10-01T10:38:24Z"
}

Key things to notice:

  • end_date_time is null while the session is in progress. When it gets a value, the session has ended.
  • kwh is the running total of energy delivered, updated continuously.
  • charging_periods is the detailed timeline — multiple segments if the rate changed or different tariff elements applied.
  • status captures the session lifecycle.

Status lifecycle

Sessions progress through several statuses:

  • PENDING — session is set up but charging hasn’t actually started (e.g., authentication or vehicle handshake in progress)
  • RESERVATION — the session was started by a reservation; charging has not yet started
  • ACTIVE — charging is currently happening, energy is flowing
  • COMPLETED — the session has ended successfully; this is its final state
  • INVALID — the Session is declared invalid (failed authentication, hardware error) and will not be billed

Most sessions go PENDING → ACTIVE → COMPLETED. A reserved session begins at RESERVATION before it becomes ACTIVE. INVALID skips the success path.

stateDiagram-v2
    [*] --> PENDING: Session created
    [*] --> RESERVATION: Started by a reservation
    RESERVATION --> ACTIVE: Charging begins
    PENDING --> ACTIVE: Charging begins
    PENDING --> INVALID: Auth or handshake fails
    ACTIVE --> COMPLETED: Session ends cleanly
    ACTIVE --> INVALID: Hardware fault mid-session
    COMPLETED --> [*]: CDR follows
    INVALID --> [*]: No CDR

The publishing pattern

The CPO owns Session data. They expose a Sender Interface; the eMSP exposes a Receiver Interface for pushed updates.

Initial session push

When a session starts, the CPO PUTs the full Session object:

PUT /ocpi/emsp/2.2.1/sessions/NL/ABC/SESSION_12345
Authorization: Token cpo-credentials-token

{
  "country_code": "NL",
  "party_id": "ABC",
  "id": "SESSION_12345",
  "start_date_time": "2026-10-01T10:30:00Z",
  "end_date_time": null,
  "kwh": 0,
  "cdr_token": {                    // all 5 fields required
    "country_code": "NL",
    "party_id": "ABC",
    "uid": "12345678905880",
    "type": "RFID",
    "contract_id": "NL-ABC-C12345678-X"
  },
  "auth_method": "AUTH_REQUEST",
  "location_id": "LOC1",
  "evse_uid": "EVSE1",
  "connector_id": "CONN1",
  "currency": "EUR",
  "status": "PENDING",
  "last_updated": "2026-10-01T10:30:00Z"
}

Real-time updates via PATCH

As the session progresses, the CPO sends PATCH updates with only changed fields:

PATCH /ocpi/emsp/2.2.1/sessions/NL/ABC/SESSION_12345
Authorization: Token cpo-credentials-token

{
  "kwh": 12.4,
  "status": "ACTIVE",
  "total_cost": {"excl_vat": 4.97, "incl_vat": 5.97},
  "last_updated": "2026-10-01T10:38:24Z"
}

Updates flow every minute or so during ACTIVE. The exact frequency is implementation-defined — OCPI doesn’t mandate a specific rate, but production systems typically update at least every 60 seconds.

Final update at session end

When the session ends:

PATCH /ocpi/emsp/2.2.1/sessions/NL/ABC/SESSION_12345

{
  "end_date_time": "2026-10-01T10:55:23Z",
  "kwh": 28.7,
  "status": "COMPLETED",
  "total_cost": {"excl_vat": 11.52, "incl_vat": 13.82},
  "last_updated": "2026-10-01T10:55:25Z"
}

After the COMPLETED Session update, the CPO will follow up with a CDR (Charge Detail Record) via the CDRs module — the official, immutable billing record.

A complete worked example: CPO streams, eMSP reads

The snippets above are trimmed for clarity. Here is the full life of one session on the wire — the CPO pushing the start, streaming updates, closing it out, and the eMSP reading sessions back — with the details that trip people up called out in the comments. (Comments use // and # for teaching; real JSON has no comments.)

1. Session starts — the CPO PUTs the full Session (to the eMSP)

# ── CPO ──▶ eMSP ────────────────────────────────────────────────────────────
# The CPO owns Session data, so it PUSHES to the eMSP's Receiver Interface.
# PUT = "here is the complete, authoritative Session; store it under this id."
# Use PUT once at the start (or to fully overwrite); use PATCH for every
# subsequent change so you only send what moved.

PUT /ocpi/emsp/2.2.1/sessions/NL/TNM/SESSION_12345 HTTP/1.1
Host: emsp.example.com
# Auth scheme is the literal word "Token" — NOT "Bearer" — then the credentials
# token the eMSP issued to THIS CPO during the Credentials handshake.
Authorization: Token <token-the-eMSP-issued-to-this-CPO>
Content-Type: application/json
# Recommended so both sides can trace one call across their logs; echo them back.
X-Request-ID: 12345
X-Correlation-ID: 67890

# The URL path carries {country_code}/{party_id}/{session_id}. They MUST equal
# the country_code / party_id / id in the body — a mismatch is a common reject.
{
  "country_code": "NL",             // ISO 3166-1 alpha-2 of the CPO (2 chars)
  "party_id": "TNM",                // the CPO's party id (3 chars)
  "id": "SESSION_12345",            // Session id, unique within this CPO
  "start_date_time": "2026-10-01T10:30:00Z",  // RFC 3339, UTC ("Z")
  "end_date_time": null,            // null while the session is still running
  "kwh": 0,                         // RUNNING TOTAL of energy delivered — starts at 0
  "cdr_token": {                    // identifies the customer's token
    "country_code": "NL",
    "party_id": "TNM",
    "uid": "12345678905880",        // the token uid that was presented
    "type": "RFID",
    "contract_id": "NL-TNM-C12345678-X"
  },
  // How the driver was authorized: AUTH_REQUEST (CPO asked eMSP live) |
  // COMMAND (an OCPI start_session command kicked it off) | WHITELIST (token
  // was pre-shared, no live auth needed).
  "auth_method": "AUTH_REQUEST",    // required
  // OPTIONAL. The reference the eMSP returned in its authorization response
  // (or the one you sent with a start_session COMMAND). Present here already
  // because AUTH_REQUEST authorization completed before the session was created;
  // absent only when there was no such reference (e.g. plain WHITELIST).
  "authorization_reference": "AUTH_REF_7f3c21",
  "location_id": "LOC1",            // required. The three below point at exactly one plug
  "evse_uid": "3256",               // required. The EVSE's uid within that Location
  "connector_id": "1",              // required. The Connector's id within that EVSE
  "currency": "EUR",                // required. ISO 4217; fixed for the whole session
  "status": "PENDING",              // required. Set up, but energy is not flowing yet
  // RFC 3339, UTC ("Z"). Receivers use last_updated to detect real changes and
  // to resolve out-of-order updates (newest wins). Bump it on EVERY change or
  // downstream state silently goes stale.
  "last_updated": "2026-10-01T10:30:00Z"   // required
  // The remaining OPTIONAL Session fields have no value yet in PENDING — they are
  // legitimately absent until they apply, not "omitted for brevity":
  //   end_date_time    → set when status → COMPLETED (the session has ended).
  //   meter_id         → sent once the EVSE reports its meter id (usually at the
  //                      first ACTIVE update, when metering begins).
  //   charging_periods → first appears when metering starts (status → ACTIVE);
  //                      grows as the session crosses tariff/time boundaries.
  //   total_cost       → a running estimate; appears once there is metered energy
  //                      to price (status → ACTIVE), then updates each PATCH.
}
# ── eMSP ──▶ CPO (the response) ─────────────────────────────────────────────
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-ID: 12345
X-Correlation-ID: 67890

{
  "data": {},                       // for a PUT, receivers usually echo nothing
  // THE key OCPI gotcha: HTTP 200 only means the HTTP call arrived. You must
  // ALSO check status_code INSIDE the envelope. 1000 = success. You can get
  // HTTP 200 with status_code 2001 (invalid parameters) — always read the
  // envelope, never trust the HTTP status alone. 2xxx = client error, 3xxx = server.
  "status_code": 1000,
  "status_message": "Success",
  "timestamp": "2026-10-01T10:30:01Z"
}

2. Mid-session — the CPO PATCHes only what changed

# ── CPO ──▶ eMSP ────────────────────────────────────────────────────────────
# Now the car is charging. PATCH carries ONLY the fields that moved — the eMSP
# MERGES them into the Session it already holds (do NOT treat PATCH like PUT;
# that would wipe the unsent fields).

PATCH /ocpi/emsp/2.2.1/sessions/NL/TNM/SESSION_12345 HTTP/1.1
Host: emsp.example.com
Authorization: Token <token-the-eMSP-issued-to-this-CPO>
Content-Type: application/json

{
  "status": "ACTIVE",              // PENDING → ACTIVE: energy is now flowing
  // kwh is the RUNNING TOTAL so far — NOT the delta since the last PATCH. If a
  // PATCH is missed, the next one still carries the correct absolute value.
  "kwh": 12.4,
  // OPTIONAL. First carried now that metering has started: the meter id the EVSE
  // reported. Once sent it stays constant for the session.
  "meter_id": "MTR-3256-A",
  // OPTIONAL. Unchanged since the start push; re-sent here only to show the full
  // ACTIVE shape (a real PATCH may omit fields that did not change).
  "authorization_reference": "AUTH_REF_7f3c21",
  "charging_periods": [            // OPTIONAL. The metered timeline of the session so far
    {
      "start_date_time": "2026-10-01T10:30:00Z",  // required within each period
      "dimensions": [                              // required; each has type + volume
        { "type": "ENERGY", "volume": 12.4 },  // kWh in this period
        { "type": "TIME", "volume": 0.133 }     // hours (~8 min)
      ],
      "tariff_id": "TARIFF1"       // OPTIONAL. The tariff in effect during this period
    }
  ],
  "total_cost": {                  // OPTIONAL. A running ESTIMATE, not the billable amount
    "excl_vat": 4.97,              // required within Price (amount without VAT)
    "incl_vat": 5.97               // OPTIONAL within Price (amount including VAT)
  },
  // Bump on every PATCH. The eMSP compares this to what it holds and applies
  // only if it is NEWER — that is how out-of-order PATCHes are resolved.
  "last_updated": "2026-10-01T10:38:24Z"
}
# ── eMSP ──▶ CPO (the response) ─────────────────────────────────────────────
HTTP/1.1 200 OK
Content-Type: application/json

{
  "data": {},
  "status_code": 1000,             // still check this, not just the HTTP 200
  "status_message": "Success",
  "timestamp": "2026-10-01T10:38:25Z"
}

3. Session ends — the final PATCH (→ COMPLETED)

# ── CPO ──▶ eMSP ────────────────────────────────────────────────────────────
# The driver unplugs. The closing PATCH sets end_date_time, the final running
# kwh total, the final status, and the last estimated cost.

PATCH /ocpi/emsp/2.2.1/sessions/NL/TNM/SESSION_12345 HTTP/1.1
Host: emsp.example.com
Authorization: Token <token-the-eMSP-issued-to-this-CPO>
Content-Type: application/json

{
  "end_date_time": "2026-10-01T10:55:23Z",  // OPTIONAL: now set → session is over
  "kwh": 28.7,                     // the FINAL running total (still absolute)
  "status": "COMPLETED",           // ACTIVE → COMPLETED: the session's end state
  "meter_id": "MTR-3256-A",        // OPTIONAL. Unchanged from ACTIVE; the EVSE's meter id
  "authorization_reference": "AUTH_REF_7f3c21",  // OPTIONAL. Same reference as the start
  // OPTIONAL. The COMPLETE final timeline — here the session crossed a tariff
  // boundary, so there are two contiguous periods (peak → off-peak).
  "charging_periods": [
    {
      "start_date_time": "2026-10-01T10:30:00Z",  // required within each period
      "dimensions": [                              // required; each has type + volume
        { "type": "ENERGY", "volume": 16.9 },  // kWh delivered in the peak period
        { "type": "TIME", "volume": 0.30 }      // hours in this period (~18 min)
      ],
      "tariff_id": "TARIFF_PEAK"   // OPTIONAL. Tariff in effect for this period
    },
    {
      "start_date_time": "2026-10-01T10:48:00Z",  // required within each period
      "dimensions": [                              // required; each has type + volume
        { "type": "ENERGY", "volume": 11.8 },  // kWh delivered in the off-peak period
        { "type": "TIME", "volume": 0.12 }      // hours in this period (~7 min)
      ],
      "tariff_id": "TARIFF_OFFPEAK"  // OPTIONAL. Switched tariffs mid-session
    }
  ],
  "total_cost": {                  // OPTIONAL. Still an estimate; bill off the CDR
    "excl_vat": 11.52,             // required within Price (amount without VAT)
    "incl_vat": 13.82              // OPTIONAL within Price (amount including VAT)
  },
  "last_updated": "2026-10-01T10:55:25Z"
}
# After COMPLETED, the CPO follows up with a CDR (via the CDRs module) — the
# immutable, billable record. The Session total_cost stays an estimate; bill off
# the CDR, not this.
# ── eMSP ──▶ CPO (the response) ─────────────────────────────────────────────
HTTP/1.1 200 OK
Content-Type: application/json

{
  "data": {},
  "status_code": 1000,
  "status_message": "Success",
  "timestamp": "2026-10-01T10:55:26Z"
}

4. The eMSP reads sessions back (GET from the CPO)

# ── eMSP ──▶ CPO ────────────────────────────────────────────────────────────
# The eMSP reads from the CPO's Sender Interface — e.g. to backfill after an
# outage or to reconcile. The list endpoint is paginated: follow the pages or
# you only ever see the first slice.

GET /ocpi/cpo/2.2.1/sessions?date_from=2026-10-01T00:00:00Z&offset=0&limit=50 HTTP/1.1
Host: cpo.example.com
# Opposite direction, opposite token: the one the CPO issued to the eMSP.
Authorization: Token <token-the-CPO-issued-to-this-eMSP>
# Query params:
#   date_from / date_to → only sessions changed in this window (delta sync)
#   offset / limit      → paging; the CPO may cap limit — read the real one back
#                          from the response headers below.
# ── CPO ──▶ eMSP (the response) ─────────────────────────────────────────────
HTTP/1.1 200 OK
Content-Type: application/json
# Pagination lives in HEADERS, not the body:
#   Link          → URL of the NEXT page; keep following until there is no "next"
#   X-Total-Count → total sessions matching the filter, across ALL pages
#   X-Limit       → the page size the CPO actually applied (can be < your limit)
Link: <https://cpo.example.com/ocpi/cpo/2.2.1/sessions?date_from=2026-10-01T00:00:00Z&offset=50&limit=50>; rel="next"
X-Total-Count: 213
X-Limit: 50

{
  "data": [                        // an ARRAY of Session objects (this page only)
    {
      // The CPO returns the COMPLETE Session object — every field it holds. This
      // one is COMPLETED, so all the optional fields have their final values.
      "country_code": "NL",        // required
      "party_id": "TNM",           // required
      "id": "SESSION_12345",       // required
      "start_date_time": "2026-10-01T10:30:00Z",  // required
      "end_date_time": "2026-10-01T10:55:23Z",    // OPTIONAL; set because COMPLETED
      "kwh": 28.7,                 // required. The running total at the last update the CPO holds
      "cdr_token": {               // required. Identifies the customer's token (all 5 fields required)
        "country_code": "NL",      // country_code of the eMSP that issued the token
        "party_id": "TNM",         // party_id that issued/owns the token
        "uid": "12345678905880",   // the token uid that was presented
        "type": "RFID",            // AD_HOC_USER | APP_USER | OTHER | RFID
        "contract_id": "NL-TNM-C12345678-X"  // the eMA-I / contract id of the driver
      },
      "auth_method": "AUTH_REQUEST",  // required
      "authorization_reference": "AUTH_REF_7f3c21",  // OPTIONAL; the authorization reference
      "location_id": "LOC1",       // required
      "evse_uid": "3256",          // required
      "connector_id": "1",         // required
      "meter_id": "MTR-3256-A",    // OPTIONAL; the EVSE's meter id
      "currency": "EUR",           // required. ISO 4217
      "charging_periods": [        // OPTIONAL. The complete final metered timeline
        {
          "start_date_time": "2026-10-01T10:30:00Z",  // required within each period
          "dimensions": [                              // required; each has type + volume
            { "type": "ENERGY", "volume": 16.9 },  // kWh in the peak period
            { "type": "TIME", "volume": 0.30 }      // hours in this period
          ],
          "tariff_id": "TARIFF_PEAK"   // OPTIONAL. Tariff in effect for this period
        },
        {
          "start_date_time": "2026-10-01T10:48:00Z",  // required within each period
          "dimensions": [                              // required; each has type + volume
            { "type": "ENERGY", "volume": 11.8 },  // kWh in the off-peak period
            { "type": "TIME", "volume": 0.12 }      // hours in this period
          ],
          "tariff_id": "TARIFF_OFFPEAK"  // OPTIONAL. Switched tariffs mid-session
        }
      ],
      "total_cost": {              // OPTIONAL. The last estimate; the CDR is the billable value
        "excl_vat": 11.52,         // required within Price (amount without VAT)
        "incl_vat": 13.82          // OPTIONAL within Price (amount including VAT)
      },
      "status": "COMPLETED",       // required
      "last_updated": "2026-10-01T10:55:25Z"   // required
    }
    // … up to `limit` Sessions, then follow the Link header for the next page.
  ],
  "status_code": 1000,             // again: check this, not just the HTTP 200
  "status_message": "Success",
  "timestamp": "2026-10-01T11:00:00Z"
}

Sessions vs CDRs — the crucial distinction

A common source of confusion. Both objects describe charging sessions, but they serve different purposes:

SessionCDR
WhenDuring and just after a charging sessionAfter the session has fully completed
MutableYes — updated many timesNo — immutable once created
PurposeLive UX, real-time monitoringBilling, accounting, audit trail
FrequencyMultiple per session (updates)One per session (final)
LifecyclePENDING → ACTIVE → COMPLETED/INVALIDCreated once after session ends

The Session is the running narrative; the CDR is the closing statement.

Charging periods — the granular timeline

The charging_periods array within a Session captures detail about how the session unfolded:

"charging_periods": [
  {
    "start_date_time": "2026-10-01T10:30:00Z",
    "dimensions": [
      {"type": "ENERGY", "volume": 5.2},
      {"type": "TIME", "volume": 0.083}
    ],
    "tariff_id": "TARIFF_PEAK"
  },
  {
    "start_date_time": "2026-10-01T10:38:00Z",
    "dimensions": [
      {"type": "ENERGY", "volume": 7.2},
      {"type": "TIME", "volume": 0.117}
    ],
    "tariff_id": "TARIFF_OFFPEAK"   // switched tariffs mid-session
  }
]

Each period has:

  • A start time
  • Dimensions (kWh delivered, time elapsed, current parking time, etc.)
  • The tariff in effect during that period

Multiple periods occur when:

  • The session crosses a time-of-use boundary (peak → off-peak)
  • The session crosses an idle/charging boundary
  • Pricing changes mid-session (rare but possible)

Auth method and authorization reference

The auth_method and authorization_reference fields capture how the driver was authorized to charge:

  • AUTH_REQUEST — the CPO asked the eMSP via OCPI; the eMSP authorized
  • COMMAND — an OCPI Command (start_session) initiated this session, so the eMSP pre-authorized
  • WHITELIST — the token was on a pre-shared whitelist (no live auth needed)

authorization_reference is a CPO-assigned identifier that lets you tie the session back to the authorization event.

How the eMSP uses Session data

The eMSP’s app uses Session data to:

  1. Show “currently charging” status to the driver — “Your session at Site X started at 10:30, delivered 12.4 kWh so far”
  2. Display live progress — “Charging at 47 kW, estimated 22 minutes to 80%”
  3. Compute predicted cost — based on the running kWh and the applicable Tariff
  4. Send push notifications at milestones (session start, 80% charged, session ended)
  5. Trigger end-of-session flows — final cost confirmation, email receipt setup

When the Session status transitions to COMPLETED, the app typically waits a few minutes for the corresponding CDR to arrive, then surfaces the final, immutable bill.

Implementation gotchas

kwh must be a running total, not a delta — the #1 mistake

The single most common Sessions bug is treating kwh as the energy delivered since the last PATCH. It is not. kwh is always the absolute running total for the whole session so far. This matters because updates are lossy and unordered:

  • A PATCH gets missed (network blip). If kwh were a delta, the eMSP would now be permanently short by that increment. Because it is a running total, the very next PATCH silently brings the eMSP back to the correct absolute value — no reconciliation needed.
  • Two PATCHes arrive out of order. The tiebreaker is last_updatednewer wins. Applying an older PATCH after a newer one would clobber fresh data (the classic “the session went backwards” bug). Always compare last_updated before applying; drop the update if what you already hold is newer.

Concretely, a correct sequence — even with a dropped update in the middle:

// PATCH #1  last_updated 10:38:24Z  → kwh 12.4   (apply: newer than what we hold)
// PATCH #2  last_updated 10:45:10Z  → kwh 20.1   (LOST in transit — never arrives)
// PATCH #3  last_updated 10:52:33Z  → kwh 26.8   (apply: still the absolute total,
//                                                 so we jump straight to 26.8 — no
//                                                 need to have seen #2)

// And if #3 had arrived BEFORE a delayed #1:
//   hold 26.8 (last_updated 10:52:33Z), then #1 shows up (10:38:24Z) →
//   10:38:24Z < 10:52:33Z, so DROP #1. Do not overwrite 26.8 with 12.4.

Because each PATCH carries the latest accurate absolute value, a single received update is always enough to make the eMSP current — provided it is applied only when it is the newest one seen. See the OCPI 2.2.1 spec’s Sessions module for the object definition and the last_updated semantics.

Reconciliation against the CDR

A common bug: the Session shows total_cost.incl_vat = 13.82 but the CDR shows 14.15. Some difference is acceptable (the Session is an estimate; the CDR is final and may include rounding or last-second updates), but big gaps signal a bug. Reconciliation alerts should fire on differences over a threshold.

Sessions for unfinished charges

If a CPO marks a Session as COMPLETED but never sends a CDR, the eMSP doesn’t know what to bill. Implementations need a timeout: if a CDR doesn’t arrive within X hours of session completion, alert or fall back to billing based on the Session data (with a flag).

Charging periods overlap

The charging_periods array should be non-overlapping and time-contiguous. Bugs in production CDRs sometimes create gaps or overlaps. Validation at receive time catches this.

Three things to take from this

  1. Sessions are the live event stream. They update during the session, get a final update at end, then the CDR follows.

  2. Status transitions matter. PENDING → ACTIVE → COMPLETED is the happy path. PATCH updates carry only the changed fields — but kwh is always the absolute running total, and the last_updated timestamps are critical for ordering.

  3. Sessions are not Billing. The CDR is. Use Sessions for live UX; use CDRs for accounting and dispute resolution.

The Sessions module is what makes EV charging feel responsive in a customer-facing app. Get the update cadence and the PATCH semantics right and the experience is smooth.

Quick check

Q1. A Session's status is PENDING. What does that mean?

Frequently asked questions

What is the OCPI Sessions module for?

It carries live data about an in-progress charging session from CPO to eMSP — start time, current energy delivered, charging power, and final state. It lets eMSP apps show drivers their charging progress in real time.

How is a Session different from a CDR?

A Session is live, in-progress data (or just-completed). A CDR (Charge Detail Record) is the final, immutable billing record created after a Session ends. The two have related but different roles.

Found this useful? Share it.