---
updatedAt: 2026-10-28T10:11:20.000Z
agentTools:
  projectIndex: https://developers.solidstudio.io/llms.txt
---

# 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.

```mermaid
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.

```bash
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"
```

```json
{
  "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:

```json
{
  "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](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". |

```mermaid
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
```

```bash
curl -s -X POST \
  -H "Authorization: $API_KEY" \
  "https://<host>/backoffice/sessions/3f1c2a9e-0000-4000-8000-000000000001/stop"
```

```json
{
  "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](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.

```bash
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 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`. |

```bash
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"
```

```json
{
  "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:

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.

```bash
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": ["finance@example.com"] }' \
  "https://<host>/backoffice/reports/cdr"
```

## Related

* [getting-started.md](getting-started.md)
* [authorization.md](authorization.md)
* [payments-settlement.md](payments-settlement.md)
* [customers-tokens.md](customers-tokens.md)
* [charging-network.md](charging-network.md)
* [tariffs-coupons.md](tariffs-coupons.md)