Deep dive · In the raw · optional

One CDR, field by field

About 12 min · 3 questions ·

Thursday
The situation

The session of stops 04 to 06, this time as the record itself: the charge detail record that the operator's backend built 35 seconds after the cable came out and pushed to the provider over OCPI.

Every field is shown. The provider's systems will read it tonight, check it and turn it into a line on next month's invoice.

The questionCan you read the record field by field: who wrote it, what was measured, what the operator charges, and how the provider checks it?

Guess firstThe CDR states a total of 25.39 EUR. Is that what the driver pays?RevealHide

No. It is what the operator charges the provider under their roaming agreement, before VAT. The driver pays the provider's contract price, 0.61 EUR per kWh with no parking fee in the first 15 minutes: 25.01 EUR (stop 05).

What you are looking at

A is one JSON object, defined field by field in the CDRs module of . The operator sends it once the session is complete; the provider stores it, checks it and bills from it. Timestamps are UTC, as everywhere in OCPI.

The record below follows OCPI 2.3.0. Select the numbered lines to see what each block is for and where its values come from.

OCPI 2.3.0, CDRs module, pushed by the operatorThe record, field by field

The operator ALP in Austria wrote it; the token belongs to the provider NOR in Germany. The composed values match stops 04 to 06.

  1. {
  2. Line 2Who wrote this record

    A CDR belongs to the party that created it: the operator of the charger, identified by country code and a three-letter party ID. Together with the id below, this makes the record unique across all of roaming.

  3. "party_id": "ALP",
  4. "end_date_time": "2026-10-08T14:27:30Z",
  5. "session_id": "S-20261008-134112-3256-1",
  6. "country_code": "DE",
  7. "party_id": "NOR",
  8. "type": "RFID",
  9. },
  10. "id": "LOC-A1-EAST-14",
  11. "name": "Motorway services A1 east",
  12. "address": "Rastplatzstrasse 1",
  13. "city": "Ansfelden",
  14. "postal_code": "4052",
  15. "coordinates": { "latitude": "48.2108", "longitude": "14.2797" },
  16. "evse_uid": "3256",
  17. "connector_id": "1",
  18. "connector_format": "CABLE",
  19. "connector_power_type": "DC"
  20. },
  21. "meter_id": "AT-ALP-3256-M1",
  22. "currency": "EUR",
  23. "country_code": "AT",
  24. "party_id": "ALP",
  25. "id": "DC-STD-2026-10",
  26. "currency": "EUR",
  27. "type": "REGULAR",
  28. "elements": [
  29. ],
  30. "last_updated": "2026-10-01T06:00:00Z"
  31. }],
  32. { "start_date_time": "2026-10-08T13:41:12Z",
  33. "tariff_id": "DC-STD-2026-10" },
  34. { "start_date_time": "2026-10-08T14:15:30Z",
  35. "tariff_id": "DC-STD-2026-10" }
  36. ],
  37. "total_energy": 41.000,
  38. "total_parking_time": 0.2000,
  39. "total_parking_cost": { "before_taxes": 1.20, "taxes": [{ "name": "VAT", "percentage": 20.0, "amount": 0.24 }] },
  40. }
cdr-ALP-CDR-2026-0417-00913.json: the whole file, to copy
{
  "country_code": "AT",
  "party_id": "ALP",
  "id": "ALP-CDR-2026-0417-00913",
  "start_date_time": "2026-10-08T13:41:12Z",
  "end_date_time": "2026-10-08T14:27:30Z",
  "session_id": "S-20261008-134112-3256-1",
  "cdr_token": {
    "country_code": "DE",
    "party_id": "NOR",
    "uid": "04A2B3C4D5E680",
    "type": "RFID",
    "contract_id": "DE-NOR-C8A2B3C4D-2"
  },
  "auth_method": "WHITELIST",
  "cdr_location": {
    "id": "LOC-A1-EAST-14",
    "name": "Motorway services A1 east",
    "address": "Rastplatzstrasse 1",
    "city": "Ansfelden",
    "postal_code": "4052",
    "country": "AUT",
    "coordinates": { "latitude": "48.2108", "longitude": "14.2797" },
    "evse_uid": "3256",
    "evse_id": "AT*ALP*E003256*2",
    "connector_id": "1",
    "connector_standard": "IEC_62196_T2_COMBO",
    "connector_format": "CABLE",
    "connector_power_type": "DC"
  },
  "meter_id": "AT-ALP-3256-M1",
  "currency": "EUR",
  "tariffs": [{
    "country_code": "AT",
    "party_id": "ALP",
    "id": "DC-STD-2026-10",
    "currency": "EUR",
    "type": "REGULAR",
    "tax_included": "NO",
    "elements": [
      { "price_components": [{ "type": "ENERGY", "price": 0.59, "vat": 20.0, "step_size": 1 }] },
      { "price_components": [{ "type": "PARKING_TIME", "price": 6.00, "vat": 20.0, "step_size": 60 }] }
    ],
    "last_updated": "2026-10-01T06:00:00Z"
  }],
  "charging_periods": [
    { "start_date_time": "2026-10-08T13:41:12Z",
      "dimensions": [{ "type": "ENERGY", "volume": 41.000 }, { "type": "TIME", "volume": 0.5717 }],
      "tariff_id": "DC-STD-2026-10" },
    { "start_date_time": "2026-10-08T14:15:30Z",
      "dimensions": [{ "type": "PARKING_TIME", "volume": 0.2000 }],
      "tariff_id": "DC-STD-2026-10" }
  ],
  "total_cost": { "before_taxes": 25.39, "taxes": [{ "name": "VAT", "percentage": 20.0, "amount": 5.08 }] },
  "total_energy": 41.000,
  "total_energy_cost": { "before_taxes": 24.19, "taxes": [{ "name": "VAT", "percentage": 20.0, "amount": 4.84 }] },
  "total_time": 0.7717,
  "total_parking_time": 0.2000,
  "total_parking_cost": { "before_taxes": 1.20, "taxes": [{ "name": "VAT", "percentage": 20.0, "amount": 0.24 }] },
  "credit": false,
  "last_updated": "2026-10-08T14:28:05Z"
}

A composed record in the structure of the OCPI 2.3.0 CDR object. The tariff dimensions and step sizes follow the Tariffs module, the token the Tokens module, the price and tax classes the common types. Austria's standard VAT rate is 20%. The contract ID follows the eMAID format with its check digit, as the ID registration organisations document it.

Where each block comes from

Nothing in the record is typed by hand. Each block has one source:

  • Header. The operator's identity and an id it never reuses. The provider stores records by these two.
  • Token. Copied from what the provider shared beforehand. The uid is the card the charger read, the what the provider bills.
  • Location. The operator's Locations data, cut down to the one EVSE and connector that were used.
  • Tariff. The operator's tariff as it applied, copied in full so the record stays complete when prices change.
  • Periods and totals. Computed from the charger's meter readings and status messages, the side of the same session.

Three checks the provider runs

Before a CDR becomes an invoice line, the provider's system tests it:

  • Is the mine? The country code, party ID and uid must match a token it shared, and the contract must have been valid at start_date_time.
  • Is the tariff the one I know? The copied tariff is compared with the version received through the Tariffs module. A mismatch is a dispute before it becomes one.
  • Do the numbers add up? 41 kWh at 0.59 EUR is 24.19 EUR; 0.2 hours at 6.00 EUR is 1.20 EUR; 25.39 EUR in total, plus 20% VAT. Then the provider applies its own contract price to the same dimensions: 41 kWh at 0.61 EUR, no parking fee within 15 minutes, 25.01 EUR for the driver.

Records that fail a check go to a queue for a person; the rest are invoiced automatically. How the two companies settle the month is shown in Payments and billing, stop 05.

If the record had to be correctedThe credit CDR, head and totals

Stop 06 told the story of a parking fee counted twice. This is what the cancellation of a record looks like; a corrected record with a new id follows it.

  1. {
  2. "country_code": "AT",
  3. "party_id": "ALP",
  4. Line 4A new id for the credit

    The credit is a record of its own, with its own id. Nothing in the original changes.

  5. "credit_reference_id": "ALP-CDR-2026-0417-00913",
  6. … the remaining fields repeat the original record …
  7. }

OCPI does not allow a CDR to be deleted: a credit CDR cancels it, and a new record replaces it.

Check your understanding

3 questions. Answer all of them to complete this stop. Each answer explains itself.

01Question 1 of 3

The CDR repeats the whole tariff, although the provider received it earlier through the Tariffs module. Why?

02Question 2 of 3

total_time is 0.7717 hours, but the energy period lasted 0.5717 hours. What fills the difference?

03Question 3 of 3

cdr_token carries party_id NOR, the record itself party_id ALP. Which company is which?

  1. EVRoaming Foundation: OCPI 2.3.0, CDRs module: credit CDRs, push and pull model. https://github.com/ocpi/ocpi/blob/2.3.0/release/core/mod_cdrs.asciidoc. Checked 09 Oct 2026.
  2. EVRoaming Foundation: OCPI 2.3.0, types: DateTime in UTC, the Price and TaxAmount classes. https://github.com/ocpi/ocpi/blob/2.3.0/release/core/types.asciidoc. Checked 11 Oct 2026.
  3. EVRoaming Foundation: OCPI 2.3.0, CDRs module: credit CDRs, push and pull model. https://github.com/ocpi/ocpi/blob/2.3.0/release/core/mod_cdrs.asciidoc. Checked 09 Oct 2026.
  4. EVRoaming Foundation: OCPI 2.3.0, Tariffs module: tariff dimension types. https://github.com/ocpi/ocpi/blob/2.3.0/release/core/mod_tariffs.asciidoc. Checked 09 Oct 2026.
  5. EVRoaming Foundation: OCPI 2.3.0, Tokens module: whitelist types and real-time authorisation. https://github.com/ocpi/ocpi/blob/2.3.0/release/core/mod_tokens.asciidoc. Checked 09 Oct 2026.
  6. EVRoaming Foundation: OCPI 2.3.0, types: DateTime in UTC, the Price and TaxAmount classes. https://github.com/ocpi/ocpi/blob/2.3.0/release/core/types.asciidoc. Checked 11 Oct 2026.
  7. European Commission, Taxation and Customs Union: VAT rates applied in the Member States of the European Union. https://taxation-customs.ec.europa.eu/taxation/vat/vat-directive/vat-rates_en. Checked 11 Oct 2026.
  8. Benelux IDRO: E-mobility ID-codes: the purpose of IDs, ID usage and ID format, November 2023, with Annex 1 on the contract ID check digit. https://benelux-idro.eu/sites/default/files/2024-03/IDRR-Emobility%20ID%20Format%20and%20Syntax_November%202023.pdf. Checked 11 Oct 2026.
  9. EVRoaming Foundation: OCPI 2.3.0, CDRs module: credit CDRs, push and pull model. https://github.com/ocpi/ocpi/blob/2.3.0/release/core/mod_cdrs.asciidoc. Checked 09 Oct 2026.
  10. EVRoaming Foundation: OCPI 2.3.0, CDRs module: credit CDRs, push and pull model. https://github.com/ocpi/ocpi/blob/2.3.0/release/core/mod_cdrs.asciidoc. Checked 09 Oct 2026.
Was this helpful?