DRAFT — not indexed by search engines. Visible only via direct URL or the admin page.

OCPI Tariff Dimensions Explained: Pricing, Rules & Examples

OCPI tariff dimensions explained: ENERGY, TIME, FLAT, PARKING_TIME, step_size rounding, restrictions, and real CPO pricing patterns — with worked examples.

OCPI tariff dimensions are the building blocks of every EV charging bill. When a driver in Amsterdam or Atlanta pays for a session, the amount is assembled from tariff dimensions — energy, time, a session fee, an idle fee — each applied under its own rules. Get the dimensions right and billing is deterministic and disputable; get them wrong and you produce charges users notice. This guide covers every tariff dimension in OCPI 2.2.1, how step_size rounding works, how restrictions narrow when a dimension applies, and the real CPO pricing patterns they enable — with worked examples you can reconcile by hand.

This deep dive sits under the OCPI Tariffs module. If you are new to the protocol, start with what OCPI is; if you want to see how tariffs relate to the charger-side protocols, see OCPI vs OCPP vs ISO 15118.

The four tariff dimensions

A tariff’s pricing lives in price_components, and each component has a type drawn from the OCPI TariffDimensionType enum. OCPI 2.2.1 defines exactly four:

ENERGY — cost per kWh delivered. step_size is a multiplier of 1 Wh. TIME — cost for time while charging. Defined in hours; step_size is a multiplier of 1 second. PARKING_TIME — cost for time plugged in but not actively charging (the basis for idle fees). Defined in hours; step_size is a multiplier of 1 second. FLAT — a fixed fee with no unit. step_size has no meaningful unit here.

That is the complete list. There is no RESERVATION_TIME, IDLE_TIME, or POWER price dimension — those names appear elsewhere in OCPI (as CDR measurement dimensions), but they are not something you can put in a tariff’s price_components. Reservation pricing is handled differently, using the TIME and FLAT dimensions with a reservation restriction; see Pricing a reservation below.

Each price component carries:

  • price — per-unit cost in the tariff’s currency.
  • step_size — the minimum billable increment, expressed as a multiplier of the dimension’s base unit (1 Wh for ENERGY, 1 second for TIME/PARKING_TIME).
  • vat — the applicable VAT percentage for this dimension, if the tariff prices tax per component.

The mix of dimensions a session actually incurs is what ends up on the CDR, so the way these components combine determines the final bill:

flowchart LR
  S[Session] --> E[ENERGY<br/>per kWh]
  S --> T[TIME<br/>per second charging]
  S --> F[FLAT<br/>per session]
  S --> P[PARKING_TIME<br/>idle fee]
  E --> C[CDR total cost]
  T --> C
  F --> C
  P --> C
  style C fill:#e0f2fe,stroke:#0369a1

A typical tariff structure

A simple tariff with ENERGY and FLAT:

{
  "id": "tariff-1",
  "currency": "EUR",
  "type": "REGULAR",
  "elements": [
    {
      "price_components": [
        { "type": "ENERGY", "price": 0.35, "step_size": 1 },
        { "type": "FLAT", "price": 1.00, "step_size": 1 }
      ]
    }
  ]
}

This says: €0.35 per kWh, plus a €1 session fee. A 20 kWh session costs €1 + (20 × €0.35) = €8.

The elements array can contain multiple elements with different restrictions. A common pattern: one element for peak hours, another for off-peak.

Restrictions: narrowing when dimensions apply

The restrictions object on each element controls when its price_components apply.

Common restrictions:

day_of_week — list of days (MONDAY, TUESDAY, etc.) when this element applies.

start_time / end_time — time-of-day range (HH:MM). Use for peak/off-peak.

start_date / end_date — date range. Use for seasonal pricing.

min_kwh / max_kwh — applies only when session energy is within this range.

min_current / max_current — applies only at certain charging currents.

min_power / max_power — applies only at certain power levels.

min_duration / max_duration — applies only for sessions of certain length.

reservation — a ReservationRestrictionType that marks an element as applying to reservation cost rather than the charging session itself (covered in Pricing a reservation below).

A peak/off-peak tariff:

{
  "elements": [
    {
      "restrictions": {
        "day_of_week": ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY"],
        "start_time": "07:00",
        "end_time": "20:00"
      },
      "price_components": [
        { "type": "ENERGY", "price": 0.45, "step_size": 1 }
      ]
    },
    {
      "restrictions": {
        "day_of_week": ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY"],
        "start_time": "20:00",
        "end_time": "07:00"
      },
      "price_components": [
        { "type": "ENERGY", "price": 0.25, "step_size": 1 }
      ]
    },
    {
      "restrictions": {
        "day_of_week": ["SATURDAY", "SUNDAY"]
      },
      "price_components": [
        { "type": "ENERGY", "price": 0.30, "step_size": 1 }
      ]
    }
  ]
}

Three elements: weekday-peak (€0.45), weekday-off-peak (€0.25), weekend-flat (€0.30). The session’s actual cost depends on when energy flowed.

Pricing a reservation

There is no RESERVATION_TIME price dimension. Instead, OCPI 2.2.1 prices a reservation by adding a tariff element with a reservation restriction, and that reserved-only element may use only the FLAT and TIME dimensions — where TIME here means the duration of the reservation, not charging time. A flat reservation fee plus a per-hour hold charge looks like this:

{
  "elements": [
    {
      "restrictions": { "reservation": "RESERVATION" },
      "price_components": [
        { "type": "FLAT", "price": 0.50, "step_size": 1 },
        { "type": "TIME", "price": 5.00, "step_size": 60 }
      ]
    },
    {
      "price_components": [
        { "type": "ENERGY", "price": 0.35, "step_size": 1000 }
      ]
    }
  ]
}

The first element only applies to the reservation: a €0.50 flat hold plus €5.00 per hour of reserved time, billed in 60-second steps. The second element prices the actual charging. This is how you model “we hold the stall for you, and there is a fee if you reserve” without inventing a dimension the protocol does not have.

Step sizes and rounding

The step_size field controls billing granularity.

For ENERGY: step_size in Wh. A step_size of 1000 means billing per kWh increment. A step_size of 1 means per Wh.

For TIME: step_size in seconds. A step_size of 900 means per 15-minute increment (round up).

For FLAT: typically 1 (it’s a flat fee — no step).

How step sizes work: each consumption unit is rounded up to the next step. So a session that delivered 5.3 kWh on a step_size of 1000 Wh bills as 6 kWh (round up to the next 1000 Wh boundary).

This can surprise users. A 10-minute session billed in 15-minute increments charges for 15 minutes. Restaurants and retail sometimes use this to discourage idle parking.

Tariff types

The type field on the tariff classifies it, using the OCPI TariffType enum:

  • REGULAR — the default tariff, valid when a driver uses an RFID/token with no charging preference set.
  • AD_HOC_PAYMENT — for drive-up users paying directly at the charger (e.g., by card, not via an eMSP).
  • PROFILE_CHEAP — the tariff that applies when the driver’s charging preference is CHEAP.
  • PROFILE_FAST — the tariff that applies when the charging preference is FAST.
  • PROFILE_GREEN — the tariff that applies when the charging preference is GREEN.

A CPO can publish multiple tariffs of different types for the same connector. AD_HOC_PAYMENT tariffs matter for drive-up pricing at networks such as Electrify America, EVgo, and ChargePoint in North America and Fastned and Ionity in Europe, where a driver pays at the charger without an eMSP relationship. The PROFILE_* types are the other half of the charging preferences mechanism, covered next.

Tariff profiles and charging preferences

The PROFILE_CHEAP, PROFILE_FAST, and PROFILE_GREEN tariff types do not stand alone — they are the pricing half of OCPI 2.2.1’s Charging Preferences feature, which is defined in the Sessions module. The ProfileType enum a driver can express is CHEAP, FAST, GREEN, or REGULAR:

  • CHEAP — the driver wants the cheapest charging possible.
  • FAST — the driver wants to charge as quickly as possible and will pay a premium.
  • GREEN — the driver wants as much renewable energy as possible.
  • REGULAR — no special preference.

The flow ties the two modules together:

  1. The CPO publishes tariffs, at least one per ProfileType it supports (the spec requires that for every supported preference a matching tariff exists).
  2. The driver picks a preference in their eMSP app — say “cheapest.”
  3. The eMSP sends it on the session, via a PUT to the session’s charging_preferences endpoint, carrying a ChargingPreferences object (profile_type, and optionally departure_time, energy_need, discharge_allowed).
  4. The CPO applies the tariff whose type matches — PROFILE_CHEAP — and confirms whether the preference can be honored.

So a road-tripper on I-95 who wants speed and a commuter in Munich who wants the cheapest overnight rate get different tariffs on the same charger, without the CPO proliferating separate parties or hidden discount fields. The preference selects the tariff; the tariff dimensions still do the pricing math.

Real-world tariff patterns

A few common pricing structures and how they map to OCPI.

Pure per-kWh (simple)

ENERGY: 0.35/kWh

One element, one dimension. Easy.

Per-kWh + session fee

ENERGY: 0.35/kWh
FLAT: 1.00 per session

Common pattern. Covers session-overhead costs (network connectivity, payment processing) via the flat fee.

Per-kWh + idle fee after a grace period

ENERGY: 0.35/kWh always
PARKING_TIME: 0.05/minute, restriction min_duration: 1800 (30 minutes after charging completes)

Users charge at the normal rate; after charging is done and they’re still plugged in, idle fees kick in 30 minutes later. Encourages cycling stalls. Tesla Supercharger, EVgo, and Electrify America all use idle-fee structures like this in North America; you see the same pattern with Fastned and Ionity in Europe. For the economics behind these fees, see peak demand charges in EV charging (coming soon).

Peak/off-peak time-of-use

Two elements with day_of_week and start_time/end_time restrictions, different ENERGY prices.

Demand-based (per-power)

ENERGY: 0.30/kWh, restriction max_power: 50 kW
ENERGY: 0.45/kWh, restriction min_power: 50 kW

Slow charging is cheaper; fast charging costs more. Encourages users who don’t need speed to charge slowly.

Tiered by session size

ENERGY: 0.40/kWh, restriction max_kwh: 20 (first 20 kWh)
ENERGY: 0.30/kWh, restriction min_kwh: 20 (above 20 kWh)

Encourages longer sessions. Used by some fleet pricing structures.

Free for short sessions

FLAT: 0, restriction max_duration: 1800 (first 30 minutes free)
FLAT: 2.00, restriction min_duration: 1800 (€2 fee after 30 minutes)
ENERGY: 0.35/kWh always

Free for quick top-ups; charged for longer sessions. Mall and retail-friendly pricing.

CDR breakdown

A CDR (Charge Detail Record) reports the cost per dimension. Each element of the tariff that applied during the session produces a line in the CDR.

A CDR might look like:

{
  "id": "cdr-12345",
  "total_cost": { "excl_vat": 8.20, "incl_vat": 9.84 },
  "total_energy": 20.5,
  "total_time": 1.0,
  "charging_periods": [
    {
      "start_date_time": "2026-06-26T19:00:00Z",
      "dimensions": [
        { "type": "ENERGY", "volume": 12.5 },
        { "type": "TIME", "volume": 1800 }
      ],
      "tariff_id": "tariff-1"
    },
    {
      "start_date_time": "2026-06-26T19:30:00Z",
      "dimensions": [
        { "type": "ENERGY", "volume": 8.0 },
        { "type": "TIME", "volume": 1800 }
      ],
      "tariff_id": "tariff-1"
    }
  ]
}

Each charging_period documents what was consumed in that period. The eMSP can reconcile the math against the tariff to verify the cost calculation.

Currency and VAT

OCPI is currency-aware. Each tariff specifies a currency (ISO 4217 code).

VAT (Value-Added Tax) is expressed as a percentage on each price component, in the optional vat field:

{ "type": "ENERGY", "price": 0.35, "step_size": 1000, "vat": 19.0 }

Here the price (€0.35) is the base amount and vat (19%) is the tax applied to it. The tariff’s tax_included flag (YES, NO, or N/A) tells receivers whether the prices already include tax. The total_cost in the CDR is a Price object that breaks the figure into excl_vat and incl_vat so the tax is always explicit.

Different jurisdictions have different VAT structures, and EV charging may be taxed differently than retail electricity — a live issue in both EU member states and across US states with varying treatment of charging-as-a-service. Get this right early: fixing tax math retroactively is expensive.

Common implementation pitfalls

A list of things that go wrong with tariff implementations.

Hard-coded dimension types. Code that only knows about ENERGY and FLAT silently drops PARKING_TIME values (and any reservation pricing). Bills come out too low, and idle fees vanish.

Wrong step_size interpretation. Treating step_size as “round to nearest” instead of “round up to next” produces bills that are slightly off.

Time zone confusion. Tariff restrictions are in the tariff’s timezone (typically UTC or the CPO’s local time). Mismatching this with the session’s actual time results in wrong tariff elements being applied.

Multiple applicable elements. Two elements both apply (e.g., overlapping restrictions). Behavior is implementation-defined. Better to design tariffs with non-overlapping elements.

Currency conversion. A tariff in EUR, a CDR in EUR, but the eMSP’s billing in USD. Conversion adds complexity and timing — at what FX rate?

Tariff updates mid-session. A CPO updates a tariff while a session is in progress. Which price applies? In practice the tariff that was in effect when the session started governs it, so billing stays deterministic — and the CDR records the applicable tariff_id on each charging period, which is what an eMSP should reconcile against rather than re-fetching the (possibly changed) live tariff.

Best practices

A short list.

Test tariff math against the CPO’s billing engine. Don’t trust your math; trust the CPO’s. They’re the source of truth.

Validate every CDR against the published tariff. Helps catch interpretation bugs early.

Maintain a per-partner tariff translation layer. Different partners may interpret restrictions slightly differently. Normalize on ingest.

Show users the tariff before they start. OCPI supports tariff query; some networks display “this session will cost approximately X” before authorization. Reduces complaints.

Audit pricing on a sample basis. Pick 100 random CDRs per month; verify the math. Catches calibration issues early.

Key takeaways

OCPI’s four tariff dimensions are expressive enough to model most real-world pricing structures. The trade-off is complexity, and there are many ways to subtly mishandle the math. The essentials to hold onto:

  • There are exactly four tariff price dimensions in OCPI 2.2.1: ENERGY, TIME, PARKING_TIME, FLAT. RESERVATION_TIME, POWER, and similar names are CDR measurement dimensions, not tariff price dimensions.
  • step_size always rounds up to the next multiple of the base unit (1 Wh for ENERGY, 1 second for TIME/PARKING_TIME) — never round to nearest.
  • Restrictions narrow when an element applies (time, day, power, energy, duration), and reservation pricing is done with FLAT/TIME under a reservation restriction, not a separate dimension.
  • The PROFILE_CHEAP/FAST/GREEN tariff types are one half of Charging Preferences, defined in the Sessions module; the driver’s preference selects which tariff applies.
  • Bill from the tariff_id on each CDR charging period, break VAT out into excl_vat/incl_vat, and audit a sample of CDRs against the published tariff.

Invest in correct, audited tariff handling and you avoid most OCPI billing disputes before they reach a driver.

For how these pricing structures have evolved across releases — and to confirm any version-specific behavior before you build on it — check the OCPI version history and the official OCPI changelog.

Quick check

Q1. Which tariff dimension charges for time when a car is plugged in but no longer actively charging?
Q2. A tariff is updated by the CPO while a session is in progress. Which tariff governs the session under OCPI?
Q3. How does OCPI apply step_size when billing a dimension?
Q4. Which tariff type is used for a drive-up customer paying directly at the charger without an eMSP?
Q5. Why should VAT and currency handling be designed correctly early in a tariff implementation?

Frequently asked questions

Why are there so many tariff dimensions?

Real-world pricing is multi-dimensional. A tariff might charge €0.35/kWh for energy, €0.05/minute after the first 30 minutes for occupancy, plus a €1 session fee. Each component is a different dimension. The flexibility lets CPOs model their actual pricing rather than approximating.

Can I stack multiple dimensions in one tariff?

Yes. A single tariff element can hold ENERGY + TIME + FLAT price components active simultaneously, each with its own price and step_size, and you split them across elements when you need different restrictions per dimension. The CDR breaks out the cost per dimension so users see what they paid for.

How do tariff restrictions work?

Restrictions narrow when a dimension applies — by day of week, time of day, state of charge, current draw, etc. A single tariff can have different prices for peak vs off-peak using restrictions on the same dimension.

How do the PROFILE_CHEAP, PROFILE_FAST, and PROFILE_GREEN tariff types work?

They are values of the tariff `type` field tied to OCPI 2.2.1 Charging Preferences. The driver picks a preference (CHEAP, FAST, or GREEN) in their app; the eMSP sends it on the session; and the CPO applies the tariff whose type matches. The spec requires that for every ProfileType a CPO supports, a matching tariff is provided.

Found this useful? Share it.