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
| Concept | What it is | Identifier |
|---|---|---|
| Charging session | One 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 |
| CDR | The 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
chargingSessionStatusV2 | Meaning |
|---|---|
PENDING_START | Start requested by the EMSP. No callback and no session data from the CPO yet. |
ACCEPTED | CPO accepted the remote start command. |
WAITING_FOR_ACTIVATION | CPO reported the session (OCPI PENDING). Communication is set up and charging has not started yet. |
ACTIVE | Charging in progress. Periodic updates keep the session ACTIVE. |
PENDING_STOP | Stop requested (by the driver or the back office) and accepted by the operator. |
WAITING_FOR_COMPLETION | Stop accepted. Waiting for the CPO to finish the session. |
COMPLETED | Session finished. A CDR follows or has already arrived. |
INVALID | Start rejected, or the session expired before it started (see below). |
CANCELLED | Cancelled before it started, or a stop was rejected by the operator. |
UNKNOWN | Status could not be determined. |
CDR_RECEIVED | Reserved. 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 param | Type | Notes |
|---|---|---|
page | int | 1-based. Default 1, min 1. |
size | int | Default 10, range 1–1000. |
user | string | Driver name or account username. |
address | string | Charging point address (country, city, street, postal code). |
statusV2 | enum[] | Repeatable. Values from the status table above. |
dateFrom / dateTo | ISO date-time, UTC | Range on the session start time. |
organizationName | string | Organization full name. |
cdrTokenType | enum[] | 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 state | Result |
|---|---|
PENDING_START, WAITING_FOR_ACTIVATION or ACCEPTED, with no CPO session data yet | No 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 ID | 400 "Charging session is not active". |
| Otherwise | A 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}/cdrsand.../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}/cdrsreturns a page of session summaries (same item shape as the sessions list).GET /backoffice/organizations/{organizationId}/cdrs/summaryreturns totals per operator:operatorName,currency,sessionCount,totalEnergyInKwh,totalChargingTimeInMinutes,totalCost.
| Query param | Notes |
|---|---|
dateFrom, dateTo | Required, ISO date-time. |
address, username, operatorName | Optional, max 255 chars. |
paymentStatuses | Optional, up to 10 PaymentStatus values. |
page, size | List 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 field | Notes |
|---|---|
dateFrom, dateTo | Required, ISO date-time, validated as a date range. Selects CDRs by date of CDR receipt (cdr.created_at), not by session start. |
recipients | Optional set of e-mail addresses. Each gets an e-mail with a download link. |
Flow:
- The backbone queries the CDRs in the range (newest first) and writes them as CSV.
- The CSV is uploaded to the configured S3 bucket as
cdr/report-<dateFrom>-<dateTo>.csv. - Each address in
recipientsreceives an e-mail (templatereports-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
Updated 14 days ago