Core concepts
The vocabulary covered the words OCPP gave us. This page covers the ones we added, and how they nest.
Every API call is scoped by some part of this hierarchy, so it is worth getting straight before you write anything.
Organization the account. Billing and membership.
└─ Environment the isolation boundary. prod, staging, dev.
├─ Site a physical location.
│ └─ Circuit the electrical tree feeding it.
└─ Charger a physical charging station.
└─ EVSE can charge one vehicle at a time.
└─ Connector a single socket or tethered cable.
The top four are ours. The bottom three are OCPP's — see How Flowion maps onto OCPP.
Organization#
The root of everything, and the unit of billing and membership. An organization's bill is the sum of usage across all of its environments; there is no separate billing per environment.
It has a name (free-form, for humans) and a slug — a URL-safe identifier you choose, not derived from the name. Slugs are lowercase alphanumeric segments joined by single hyphens, 1–63 characters: a DNS label. They are globally unique, and they appear in every charger's connection URL, so pick one you can live with.
Creating an organization does three things at once: it creates the
organization, creates a default environment named prod, and makes you its
Owner.
Environment#
An environment groups a fleet within an organization — prod, staging,
dev, or anything else. Names are free-form; nothing is special-cased.
Environments are the isolation boundary. This is the concept to reach for when you think "tenant". Chargers, sites, transactions and authorization tokens all belong to exactly one environment, and access is granted per environment. An organization is a grouping above that line, not itself a wall.
Exactly one environment per organization is the default at any time. It is
usually prod, but that is convention rather than a rule, and the default is
what a charger connects to when its URL names no environment.
An environment slug is unique only within its organization, so two
organizations may both have a staging.
Environments also carry two settings that matter for anything time- or
price-aware: a price zone (an ENTSO-E bidding zone, for day-ahead
electricity prices) and a time zone (an IANA name like
Europe/Stockholm, the zone an operator reads timestamps in). Both may be
unset.
Environments goes into how to use them.
Charger#
A charger is one physical charging station, belonging to exactly one environment.
It has a charge point id — the identity the station itself uses on the
wire, and the last segment of its connection URL. This is unique per
environment, not globally: two environments, even in different
organizations, may both contain a charger called CP-01. Everything inside
the platform therefore addresses a charger by its UUID, never by its charge
point id; the string only appears at the two edges, in the connection URL and
in OCPP messages themselves.
Provisioning a charger returns an authorization key — the password half of its credentials, a 40-character hex string. It is returned exactly once. Only an Argon2id hash is stored, so there is no way to read it back; if you lose it, rotate it and configure the station with the new one.
A charger carries two different notions of "is it working":
| Field | Means |
|---|---|
status | online / offline — whether some gateway currently holds its socket |
last_seen | The last time it communicated at all |
registration_status | Accepted / Pending / Rejected — whether you permit it to operate |
ocpp_version | What it actually negotiated on connect |
status is a cache, not a source of truth: a gateway killed without
unwinding can leave a stale row behind, so online can briefly lag reality.
last_seen is the durable complement — use it to judge whether a suspiciously
long-lived connection is real.
registration_status is a decision you own, not something derived from the
station's behaviour. It defaults to Accepted; moving a charger to Pending
or Rejected is how you withhold acceptance. Be careful with Rejected — see
Lifecycle of a charging session
for what it does to a station.
EVSE and Connector#
These come straight from OCPP and are covered in The vocabulary. The short version: an EVSE can charge one vehicle at a time; a connector is one physical socket. A station with two cables sharing one power module is one EVSE with two connectors.
You do not create these. They appear as chargers report themselves, which is why a freshly provisioned charger has none until it connects.
Site#
A site is a physical location — a depot, a car park, a building. It nests under an environment, not under an organization directly, because environment is the isolation boundary and a site is a grouping within it.
A site has no effect on how a charger connects. It is a data-model grouping, not a routing concept. OCPP itself has an equivalent idea, "Charging Location", and explicitly gives it no protocol meaning.
Like environments, a site can carry its own price zone and time zone, which is what you want when one environment spans countries.
Circuit#
A circuit is the electrical wiring chargers hang off. This is the concept that makes load management possible, and it has no equivalent in OCPP at all.
Circuits form a tree per site:
- A circuit with no parent is a Main circuit — the connection to the grid. Main is only legal at the top of a site's tree.
- Every other circuit has a parent, and represents a subdivision of it.
Each circuit records a phase count (1 or 3) and a rated current in
amps. A single-phase circuit also records which of the site's three grid lines
it is wired to — L1, L2 or L3 — because nested single-phase circuits
have to be capacity-checked per line. An aggregate watt figure would hide a
single-line overload. Voltage is a fixed 230 V per phase rather than a stored
value.
Two invariants are enforced on write, not merely checked at read time:
- A child's lines must be a subset of its parent's. A single-phase parent can only host single-phase children on the same line.
- The sum of a parent's immediate children's rated current, per line, must not exceed the parent's own rating on that line.
Which means it is not possible to persist a circuit tree that oversubscribes its own wiring. Expect a write that would break either rule to be rejected.
Circuit membership is per-EVSE, not per-charger. A multi-EVSE charger can have its EVSEs on different circuits, because they draw independently.
Transaction#
One charging session: one vehicle, one EVSE, one continuous visit. Covered in Lifecycle of a charging session.
Transactions are recorded as an event log — a Started event, any number
of Updated events, and an Ended event — with meter readings hanging off
them. Energy delivered is derived from the readings, not stored as a field.
Id token#
A credential a driver presents: an RFID card, a key code, an app-issued token. Id tokens live in an environment's authorization registry, which is what the CSMS consults when a station asks whether someone may charge.
An id token identifies a credential, not a person. Mapping tokens to accounts, customers or invoices is your business logic.
Members and roles#
Users belong to organizations, and each membership carries exactly one role.
On the wire the values are lowercase: owner, admin, user.
| Owner | Admin | User | |
|---|---|---|---|
| Operate chargers | all environments | all environments | granted environments only |
| Create and manage environments | ✅ | ✅ | ❌ |
| Provision chargers, manage credentials | ✅ | ✅ | ❌ |
| Manage members and access grants | ✅ | ✅ (not the Owner) | ❌ |
| Billing | ✅ | ❌ | ❌ |
There is exactly one Owner per organization, assigned when the
organization is created. Adding a member with role owner is rejected, and
there is no transfer operation yet.
Owners and Admins implicitly reach every environment. A member with role
user reaches only the environments explicitly granted to them — which is the
mechanism for "this contractor can touch staging but not prod".
How this shapes the API#
Two patterns follow from the hierarchy, and they explain most endpoint shapes:
Creation is scoped to the parent. You create an environment under an organization, a charger under an environment, a circuit under a site.
Reads by id are flat. Once something exists it has a UUID, and you fetch
it directly — /v1/chargers/{id}, /v1/sites/{id} — without repeating its
ancestry. Authorization still resolves the whole chain behind the scenes, but
you do not have to spell it out.
The API reference has every endpoint.