Tariffs & pricing
Public API — Tariffs & Pricing
How charging is priced: defining tariffs, attaching them to infrastructure (directly or via tariff rules), and where the resulting prices surface (sessions, CDRs). Field-level schemas live in the Swagger UI (/api-docs).
Permissions: tariff endpoints need tariff:read|create|update|delete; rule endpoints need tariff-rule:read|create|update|delete (see Authentication).
The pricing model
A tariff defines what to charge. Which tariff applies to a session is resolved at charging time:
flowchart LR
S[Session starts<br/>source: who charges<br/>target: where] --> R{Tariff rules<br/>highest priority first}
R -- first rule whose criteria<br/>ALL match --> T1[Rule's tariff]
R -- no rule matches --> T2[Connector's default tariff]
T1 --> P[Session price → CDR]
T2 --> P
- Connector default — every connector carries a
tariffId(set at onboarding or viaPUT /api/v1/connectors/{id}). Used when no rule matches. - Tariff rules — condition-based overrides: "OCPI roaming sessions on DC connectors get tariff X". Only rules currently inside their
validFrom/validTowindow participate; they are evaluated by descendingpriority, and the first rule whose criteria all match wins. A rule with no criteria matches everything.
The resolved tariff is recorded on the session (price, tariffId on SessionDto). The final billed amount lands in the CDR — the billing source of truth (see Session Flows).
Tariff endpoints
| Operation | Endpoint | Permission |
|---|---|---|
List (optional ?tag= filter) | GET /api/v1/tariffs | tariff:read |
| Get one | GET /api/v1/tariffs/{tariffId} | tariff:read |
| Create | POST /api/v1/tariffs → 201 + TariffDto | tariff:create |
| Replace | PUT /api/v1/tariffs/{tariffId} | tariff:update |
| Delete | DELETE /api/v1/tariffs/{tariffId} → 204 | tariff:delete |
PUT is a full replace — send the complete tariff, not a patch.
Tariff anatomy
A tariff is a list of elements; each element holds price components and optional restrictions that say when the element applies:
{
"name": "Standard AC",
"currency": "EUR",
"tags": ["public", "ac"],
"elements": [
{
"priceComponents": [
{ "type": "FLAT", "price": 0.50, "vat": 19 },
{ "type": "ENERGY", "price": 0.49, "vat": 19, "stepSize": 100 }
]
},
{
"priceComponents": [
{ "type": "PARKING_TIME", "price": 6.00, "vat": 19, "stepSize": 300 }
],
"restrictions": { "minDuration": 10800 }
}
]
}This bills €0.50 per session plus €0.49/kWh (rounded up to 0.1 kWh), and — once the session exceeds 3 hours — €6/h idle fee in 5-minute steps.
Price components
type | price is per | stepSize unit | Notes |
|---|---|---|---|
ENERGY | kWh | Wh | |
TIME | hour | seconds | charging time |
PARKING_TIME | hour | seconds | time plugged in but not charging |
FLAT | session | — (ignored) | billed once |
stepSizerounds the billed volume up to the next multiple (e.g.ENERGYwithstepSize: 1000bills whole kWh).vatis a percentage; when omitted, the environment's default VAT applies.
Restrictions
All optional; an element applies only while every restriction holds:
| Field | Unit / format |
|---|---|
startTime / endTime | time of day, "HH:MM" |
startDate / endDate | date |
dayOfWeek | array of weekday names |
minKwh / maxKwh | kWh consumed so far |
minDuration / maxDuration | seconds |
minPower / maxPower, minCurrent / maxCurrent | kW / A |
Parking-time flags
Top-level tariff booleans controlling the PARKING_TIME dimension:
| Flag | Effect |
|---|---|
calculateParkingTimeBeforeTransaction | count time between plug-in and transaction start |
calculateParkingTimeAfterTransaction | count time between transaction end and unplug |
applyParkingTimeMinDurationAsGracePeriod | treat the parking element's minDuration as a free grace period |
Tariff rules
Endpoints
| Operation | Endpoint | Permission |
|---|---|---|
List (paginated, filters/sort query) | GET /api/v1/tariffs-rules | tariff-rule:read |
| Next free priority | GET /api/v1/tariffs-rules/priority → { "next": n } | tariff-rule:read |
| Get one | GET /api/v1/tariffs-rules/{id} | tariff-rule:read |
| Matching sources (roaming partners) | GET /api/v1/tariffs-rules/{id}/sources | tariff-rule:read |
| Matching targets (connectors) | GET /api/v1/tariffs-rules/{id}/targets | tariff-rule:read |
| Create | POST /api/v1/tariffs-rules → 201 + { id } | tariff-rule:create |
| Update | PATCH /api/v1/tariffs-rules/{id} → { id } | tariff-rule:update |
| Delete | DELETE /api/v1/tariffs-rules/{id} → 204 | tariff-rule:delete |
| Source criteria catalog | GET /api/v1/tariffs-rules-dictionaries/sources | tariff-rule:read |
| Target criteria catalog | GET /api/v1/tariffs-rules-dictionaries/targets | tariff-rule:read |
Creating a rule
curl -s -X POST -H "x-api-key: $API_KEY" -H "Content-Type: application/json" \
-d '{
"name": "OCPI roaming on DC",
"tariffId": "<tariff-uuid>",
"priority": 100,
"validFrom": "2026-08-01T00:00:00Z",
"criteria": [
{ "key": "source.roamingProtocol", "operator": "in", "value": ["OCPI"] },
{ "key": "target.connector.powerType", "operator": "in", "value": ["DC"] }
]
}' \
"https://<host>/api/v1/tariffs-rules"priority≥ 0; higher wins.GET /tariffs-rules/priorityreturns the next free slot.validFrom/validToare optional; they drive the rule'sstatusin list responses:PLANNED→ACTIVE→EXPIRED. OnlyACTIVErules are evaluated.- Each criterion is
{ key, operator, value }. For theinoperator,valueis an array; string comparisons are case-insensitive. - Verify a rule's actual scope before relying on it:
GET /tariffs-rules/{id}/sourcesand/{id}/targetslist the roaming partners and connectors it currently matches.
Criteria catalog
Source — who is charging:
| Key | Operators | Values |
|---|---|---|
source.roamingProtocol | in | OCPI, OICP |
source.tokenType | in | token types |
source.tokenUid | in | token UIDs |
source.terminalProvider | eq | payment terminal providers |
source.ocpiPartnerId | in | OCPI partner IDs |
source.roamingPartnerId | in | roaming partner IDs |
source.roamingPartnerGroups | in | roaming partner group IDs |
source.roamingPartnerConnectionType | in | connection types |
Target — where the charging happens:
| Key | Operators | Values |
|---|---|---|
target.connector.id | in | connector UUIDs |
target.connector.powerType | in | AC_1_PHASE, AC_2_PHASE, AC_2_PHASE_SPLIT, AC_3_PHASE, DC |
target.connector.power | eq, lt, lte, gt, gte | number (kW) |
target.connector.groups | in | connector group IDs |
target.chargingPoint.id | eq, in | charging point UUIDs |
target.chargingPoint.ocppEvseId | in | EVSE IDs |
target.chargingPoint.groups | in | charging point group IDs |
target.chargingStation.id | eq, in | charging station UUIDs |
target.chargingStation.groups | in | charging station group IDs |
target.chargingPool.id | in | charging pool UUIDs |
target.chargingPool.groups | in | charging pool group IDs |
The dictionaries endpoints return this catalog machine-readable, including the currently valid values (entity IDs with human-readable labels) — use them to build rule editors or validate rules before creation.
Where prices surface
- Live session —
SessionDto.price(withtariffId) updates as the session progresses. - CDR — created after completion; the final billed cost. Pull via
/api/v1/cdrs, or push via aCDRwebhook.
Wrong tariff on a session? Check, in order: (1) another rule with higher priority matched; (2) the rule's validity window (status must be ACTIVE); (3) the rule's real scope via /{id}/sources + /{id}/targets; (4) the connector's default tariffId (used when no rule matched).
Related
- Infrastructure Onboarding — assigning
tariffIdwhen creating connectors - Session Flows — session
pricelifecycle, CDR creation - Webhooks — CDR push notifications
- General guide — pagination, errors, permissions catalog
Updated 7 days ago