Sessions & CDRs

How charging sessions and charge detail records (CDRs) appear in the EMSP Public API, what back-office actions exist on them, and how to export CDRs as CSV reports.

Model

ConceptWhat it isIdentifier
Charging sessionOne charging transaction of an EMSP driver on a roaming connector. Created when the driver starts charging (app, RFID, Autocharge) and updated from the CPO side while charging runs.chargingSessionId
CDRThe final, billable record of a finished session, sent by the CPO after the session ends. Payment settlement runs on the CDR.cdrId

The back office reads both through one view: a session summary carries CDR-derived fields (paymentStatus), and session details expose the linked cdrId and externalInvoiceNumber once the CDR has arrived.

Sessions and CDRs come from roaming. The EMSP does not talk to chargers directly.

Session status

chargingSessionStatusV2Meaning
PENDING_STARTStart requested by the EMSP. No callback and no session data from the CPO yet.
ACCEPTEDCPO accepted the remote start command.
WAITING_FOR_ACTIVATIONCPO reported the session (OCPI PENDING). Communication is set up and charging has not started yet.
ACTIVECharging in progress. Periodic updates keep the session ACTIVE.
PENDING_STOPStop requested (by the driver or the back office) and accepted by the operator.
WAITING_FOR_COMPLETIONStop accepted. Waiting for the CPO to finish the session.
COMPLETEDSession finished. A CDR follows or has already arrived.
INVALIDStart rejected, or the session expired before it started (see below).
CANCELLEDCancelled before it started, or a stop was rejected by the operator.
UNKNOWNStatus could not be determined.
CDR_RECEIVEDReserved. Not set by the yet.

Terminal statuses: COMPLETED, INVALID, CANCELLED. Any status can move to COMPLETED, for example during reconciliation or after missed updates.

stateDiagram-v2
    [*] --> PENDING_START: start requested
    PENDING_START --> ACCEPTED: start accepted
    PENDING_START --> WAITING_FOR_ACTIVATION: CPO reports session
    ACCEPTED --> WAITING_FOR_ACTIVATION: CPO reports session
    PENDING_START --> ACTIVE: started (RFID / Autocharge)
    ACCEPTED --> ACTIVE: started
    WAITING_FOR_ACTIVATION --> ACTIVE: started
    PENDING_START --> INVALID: rejected / expired
    ACCEPTED --> INVALID: rejected / expired
    WAITING_FOR_ACTIVATION --> INVALID: rejected
    ACTIVE --> INVALID: rejected
    PENDING_START --> CANCELLED: cancelled
    ACCEPTED --> CANCELLED: cancelled
    WAITING_FOR_ACTIVATION --> CANCELLED: cancelled
    ACTIVE --> CANCELLED: cancelled
    WAITING_FOR_ACTIVATION --> PENDING_STOP: stop requested
    ACTIVE --> PENDING_STOP: stop requested
    PENDING_STOP --> CANCELLED: stop rejected
    PENDING_STOP --> WAITING_FOR_COMPLETION: stop accepted
    PENDING_STOP --> COMPLETED: completed
    WAITING_FOR_COMPLETION --> COMPLETED: completed
    COMPLETED --> [*]: CDR received

The diagram omits a few edge cases, such as late CPO updates that move an INVALID or CANCELLED session forward.

Automatic expiry: a scheduled job finds sessions that are still PENDING_START or ACCEPTED, have no CPO session data, and are past their start timeout. It releases any payment pre-authorization or wallet hold on them and sets them to INVALID.

Listing and inspecting sessions

GET /backoffice/sessions returns a page of session summaries.

Query paramTypeNotes
pageint1-based. Default 1, min 1.
sizeintDefault 10, range 1–1000.
userstringDriver name or account username.
addressstringCharging point address (country, city, street, postal code).
statusV2enum[]Repeatable. Values from the status table above.
dateFrom / dateToISO date-time, UTCRange on the session start time.
organizationNamestringOrganization full name.
cdrTokenTypeenum[]APP_USER, RFID, OTHER.

The endpoint takes no sort parameter. GET /backoffice/sessions/filters returns the token types that the UI offers as filter values.

curl -s -G \
  -H "Authorization: $API_KEY" \
  --data-urlencode "statusV2=ACTIVE" \
  --data-urlencode "statusV2=PENDING_STOP" \
  --data-urlencode "dateFrom=2026-10-01T00:00:00" \
  --data-urlencode "size=50" \
  "https://<host>/backoffice/sessions"
{
  "content": [
    {
      "chargingSessionId": "3f1c2a9e-0000-4000-8000-000000000001",
      "chargingSessionStatusV2": "ACTIVE",
      "userId": "user-0001",
      "username": "j***@example.com",
      "chargingPointEMI3Id": "PL*EXA*E0001*1",
      "address": "Example Street 1, Warsaw",
      "countryCode": "PL",
      "totalCost": 12.30,
      "currency": "PLN",
      "consumedEnergy": 8.412,
      "startedAt": "2026-10-27T08:15:00Z",
      "operatorName": "Example CPO",
      "cdrTokenType": "APP_USER",
      "cpoSessionId": "cpo-sess-0001",
      "authorizationReference": "AUTHREF0001",
      "appliedTariffName": "Standard AC",
      "freeOfCharge": false
    }
  ],
  "totalElements": 1,
  "totalPages": 1,
  "currentPage": 1
}

totalCost and totalCostNet are running estimates while the session is open. The final amount comes from the CDR.

GET /backoffice/sessions/{id} returns { "summary": { ... }, "details": { ... } }. details adds charging point power, current power, state of charge, car make and model, plus the CDR link fields:

{
  "summary": { "chargingSessionId": "3f1c2a9e-0000-4000-8000-000000000001", "chargingSessionStatusV2": "COMPLETED", "paymentStatus": "PAID" },
  "details": {
    "chargingPointPowerInKw": 22,
    "cdrId": "cdr-0001",
    "externalInvoiceNumber": "INV/2026/10/0001",
    "freeOfCharge": false
  }
}

An unknown ID returns 404.

paymentStatus on the summary is the CDR payment status: PENDING, PAID, PAYMENT_SKIPPED, FAILED, EXEMPTED, UNKNOWN. Settlement mechanics are described in payments-settlement.md.

Stopping a session from the back office

POST /backoffice/sessions/{chargingSessionId}/stop returns 204 on success. The backbone checks the session status first:

Session stateResult
PENDING_START, WAITING_FOR_ACTIVATION or ACCEPTED, with no CPO session data yetNo command is sent. Pre-cancellation hooks run, and the session is set to CANCELLED ("Cancelled by the operator"). 204.
CANCELLED, PENDING_STOP, WAITING_FOR_COMPLETION, COMPLETED, INVALID, or unknown ID400 "Charging session is not active".
OtherwiseA remote stop is sent to the CPO through the roaming channel that owns the connector (OCPI or Hubject). If accepted: session → PENDING_STOP, 204. If rejected: session → CANCELLED with the failure reason, 400 "Remote command was rejected by the operator".
sequenceDiagram
    autonumber
    participant BO as Your backend
    participant API as EMSP Public API
    participant R as Roaming (OCPI adapter / Hubject)
    participant CPO as CPO

    BO->>API: POST /backoffice/sessions/{id}/stop
    API->>R: remote STOP_SESSION
    R->>CPO: stop command
    CPO-->>R: accepted
    API-->>BO: 204 (session PENDING_STOP)
    CPO-->>R: session update / stop result
    R-->>API: status → WAITING_FOR_COMPLETION → COMPLETED
    CPO-->>R: CDR
    R-->>API: CDR linked to session
    BO->>API: GET /backoffice/sessions/{id}
    API-->>BO: COMPLETED, details.cdrId set
curl -s -X POST \
  -H "Authorization: $API_KEY" \
  "https://<host>/backoffice/sessions/3f1c2a9e-0000-4000-8000-000000000001/stop"
{
  "code": "BAD_REQUEST",
  "message": "Cannot execute remote command. Reason: 'Charging session is not active'"
}

A 204 means the stop was accepted, not that the session has ended. Poll GET /backoffice/sessions/{id} until the status is terminal.

CDRs in the back office

CDRs do not have their own back-office resource. You reach them in three ways:

  • Through the session: GET /backoffice/sessions/{id} → details.cdrId, details.externalInvoiceNumber, summary.paymentStatus.
  • Through payments: the CDR-centric payments list, settlement details, reprocess, exempt and reschedule live under /backoffice/payments. See payments-settlement.md.
  • Per organization: GET /backoffice/organizations/{organizationId}/cdrs and .../cdrs/summary.

External reference (invoice number)

PUT /backoffice/payments/{cdrId}/external-reference stores an invoice number from an external system (ERP, accounting) on the CDR. It returns 204, or 404 for an unknown CDR. A blank value clears the field. Max length is 255.

curl -s -X PUT \
  -H "Authorization: $API_KEY" -H "Content-Type: application/json" \
  -d '{ "externalInvoiceNumber": "INV/2026/10/0001" }' \
  "https://<host>/backoffice/payments/cdr-0001/external-reference"

The value then appears as details.externalInvoiceNumber on the session.

Organization CDRs

For corporate or fleet organizations:

  • GET /backoffice/organizations/{organizationId}/cdrs returns a page of session summaries (same item shape as the sessions list).
  • GET /backoffice/organizations/{organizationId}/cdrs/summary returns totals per operator: operatorName, currency, sessionCount, totalEnergyInKwh, totalChargingTimeInMinutes, totalCost.
Query paramNotes
dateFrom, dateToRequired, ISO date-time.
address, username, operatorNameOptional, max 255 chars.
paymentStatusesOptional, up to 10 PaymentStatus values.
page, sizeList only. 1-based, default 1 / 10, size 1–1000.
curl -s -G \
  -H "Authorization: $API_KEY" \
  --data-urlencode "dateFrom=2026-10-01T00:00:00" \
  --data-urlencode "dateTo=2026-10-31T23:59:59" \
  "https://<host>/backoffice/organizations/org-0001/cdrs/summary"
{
  "operators": [
    { "operatorName": "Example CPO", "currency": "PLN", "sessionCount": 42,
      "totalEnergyInKwh": 512.250, "totalChargingTimeInMinutes": 3120, "totalCost": 1450.80 }
  ]
}

CDR reports (CSV export)

POST /backoffice/reports/cdr starts generating a CDR report. The call is asynchronous: it returns 202 Accepted with no body, and the report is built in the background.

Body fieldNotes
dateFrom, dateToRequired, ISO date-time, validated as a date range. Selects CDRs by date of CDR receipt (cdr.created_at), not by session start.
recipientsOptional set of e-mail addresses. Each gets an e-mail with a download link.

Flow:

  1. The backbone queries the CDRs in the range (newest first) and writes them as CSV.
  2. The CSV is uploaded to the configured S3 bucket as cdr/report-<dateFrom>-<dateTo>.csv.
  3. Each address in recipients receives an e-mail (template reports-cdr) with a pre-signed download URL. The URL is valid for the configured TTL (default 7 days, max 7 days).

The API never returns the file or its URL. If recipients is empty, the report is uploaded and nobody is notified.

curl -s -X POST \
  -H "Authorization: $API_KEY" -H "Content-Type: application/json" \
  -d '{ "dateFrom": "2026-10-01T00:00:00", "dateTo": "2026-10-31T23:59:59", "recipients": ["[email protected]"] }' \
  "https://<host>/backoffice/reports/cdr"

Related


Did this page help you?