OCPI uses two main interaction patterns: pull (the consumer asks for data) and push (the producer sends data when it’s ready). Different modules use different patterns, and getting the choice right per module matters for both correctness and performance.
This article explains the two patterns, which OCPI modules use which, and the operational tradeoffs. If you are new to the protocol, start with what OCPI is for the role and module basics.
The two patterns
Pull (request-response): the consumer initiates the conversation. Sends an HTTP GET to the producer’s endpoint. Gets a response with the current state.
[ Consumer ] -- GET /locations --> [ Producer ]
[ Consumer ] <-- locations data --- [ Producer ]
Pull is what you’d call a normal REST API pattern. Stateless, idempotent (in the HTTP sense), and predictable.
Push (server-initiated): the producer sends data to the consumer’s endpoint when something happens. The consumer must have a URL the producer can POST/PUT to.
[ Producer ] -- PUT /sessions/{id} --> [ Consumer ]
[ Producer ] <-- 200 OK ----------------- [ Consumer ]
Push is event-driven. The producer doesn’t wait for the consumer to ask; it sends the update right away.
OCPI uses both patterns, depending on the module and the use case.
Module-by-module pattern guide
A reference for which OCPI modules use which pattern.
Locations: pull-and-cache + push updates
Initial sync: the consumer pulls (GETs) the producer’s Locations once. Returns all current Locations. Possibly paginated.
Updates: the producer pushes individual Location updates as they happen. PUT to the consumer’s Locations endpoint with the changed Location.
In practice, many consumers do a periodic re-pull (daily or weekly) as a safety net against missed pushes. Locations data doesn’t change frequently so the redundancy is cheap.
Sessions: push primarily
The producer (CPO) pushes Session updates to the consumer (eMSP) as the session progresses.
A typical session generates 3-10 pushes:
- Session START (when the user begins charging).
- One or more interim updates (state changes, periodic energy updates).
- Session END.
Pull is also supported (the consumer can GET a specific session), but the push pattern is the primary delivery mechanism.
CDRs: push only
CDRs are the immutable billing records. The producer (CPO) pushes the CDR to the consumer (eMSP) once the session is complete and the CDR is final.
CDRs use push with retry. The producer owns delivery — if the push fails, it retries until it succeeds (or until a defined max time). The consumer must acknowledge with 200 OK.
There’s no pull pattern for CDRs in the standard sense. CDRs aren’t a queryable collection; they’re delivered events.
Tariffs: pull primarily
The producer publishes its tariffs. The consumer pulls them. The producer can also push tariff updates when they change.
In practice, tariffs change infrequently and pull-on-demand or periodic pull is common.
Tokens: query-on-demand (pull) + push for whitelisting
When a CPO needs to authorize a user identifier, it queries the eMSP via Token query (a synchronous GET or a real-time query).
Separately, the eMSP can push its whitelist (or pieces of it) to CPOs that support local authorization caching.
The pattern is mixed: real-time pulls for authorization, optional push for caching.
Commands: push (asynchronous)
A consumer (eMSP) sends a Command request to the producer (CPO). The CPO acknowledges the request, processes asynchronously, then pushes a Command response back when done.
The asynchronous nature is because commands like RemoteStart have a real-world delay — the charger needs to receive the command, react, and respond. The whole flow can take seconds to a minute.
HubClientInfo: pull-and-cache + optional push
Standard pull pattern. The hub publishes its client list; consumers pull. Optional push for subscribers who want real-time updates.
Credentials: synchronous request-response
The Credentials handshake is a pure pull/POST pattern. PUT and POST messages exchanged at registration time. No background push.
ChargingProfiles: push from eMSP to CPO
The eMSP pushes a ChargingProfile to the CPO for a specific session. The CPO acknowledges and applies it.
Why this mix
Each module’s pattern was chosen with its specific needs in mind.
Locations is mostly static data with occasional updates. Pull-and-cache is efficient. Push updates the cache without full re-pull.
Sessions are events. Push is the natural pattern — the producer knows when the event happened; the consumer wants to know immediately.
CDRs are billing-critical. Push ensures delivery; retry ensures durability. Pull would risk loss if the consumer’s polling missed a window.
Tariffs are reference data. Pull is fine; updates are infrequent.
Tokens are mixed-frequency. Real-time queries need pull (synchronous). Whitelisting needs push (push the list once, query offline).
Commands are user-initiated actions. Async push handles the real-world delay.
Choosing patterns module by module gives you the right efficiency/correctness tradeoff for each data type. The cost is that an OCPI implementation must support multiple patterns rather than one.
Push design: doing it right
If you’re implementing the receiving side of a push pattern (Sessions, CDRs, ChargingProfiles, etc.), several things matter.
Idempotency. The sender will retry on perceived failures. The same message may arrive twice. Deduplicate by the natural key (session_id for Sessions, cdr_id for CDRs).
Acknowledge fast. Return 200 OK quickly. If you have heavy processing (database writes, downstream notifications), do them async after returning.
Return meaningful errors. If you can’t process the push (validation failure, your system is down for the partner), return an OCPI StatusCode that tells the sender what to do. Permanent failure → don’t retry. Transient failure → retry later.
Authentication. Verify the bearer token on every incoming push. Don’t trust the source URL alone. See OCPI security and authentication (coming soon) for how tokens are exchanged and rotated.
Rate limiting. A misbehaving partner might push aggressively. Protect yourself with per-partner rate limits.
Logging. Log every received push for audit. Include enough detail to reconstruct what happened if a partner disputes.
Push design: doing it right (sender side)
If you’re implementing the sending side (a CPO pushing Sessions/CDRs to eMSPs):
Retry on failure. Push isn’t fire-and-forget. If the receiver returns 5xx, retry with exponential backoff. If 4xx (bad request), log and stop retrying — the receiver said “no.”
Queue and batch. Don’t fail your own system if a partner’s endpoint is down. Queue messages locally and drain when they’re back.
Per-partner retry policies. Some partners are reliable; some are flaky. Tune backoff per partner.
Deduplication keys. Generate unique IDs (session_id, cdr_id) that survive retries cleanly.
Eventual consistency. Accept that some pushes arrive late. Design downstream systems to handle late arrivals (a CDR that arrives 6 hours late is still a valid CDR).
Observability. Track per-partner push success rates, latency, retry counts. Spot problems early.
Pull design: doing it right (consumer side)
If you’re pulling from a partner’s endpoint:
Cache aggressively. Pulled data is often stable (Locations, Tariffs). Don’t re-pull on every operation.
Respect pagination. Always follow the Link header for next pages. Don’t assume the first page is everything.
Background refresh. Schedule pulls on a sensible interval (daily or weekly for Locations, more frequent for Tariffs). Don’t pull on demand for hot-path operations.
Handle 304 Not Modified. If the partner supports it, conditional GETs save bandwidth on unchanged data.
Failure tolerance. A failed pull shouldn’t break your application. Use last-known-good data and alert on staleness.
Pull design: doing it right (producer side)
If you’re serving GET endpoints:
Pagination is essential. Large result sets break the world. Default page sizes of 100-1000 are reasonable.
Filtering and search. Support date-range filters for time-series data (Sessions, CDRs).
ETags / Last-Modified. Support conditional GET so consumers can skip unchanged data.
Performance. Locations and Tariffs GETs may run frequently. Index appropriately.
Rate limiting. Even pulls can be abusive. Protect against runaway consumers.
Mixed patterns in practice
Real-world OCPI implementations often blend the patterns.
Example: a CPO running new chargers comes online.
- The CPO’s CSMS publishes new Locations.
- It pushes Location PUTs to all connected eMSPs (push for the new data).
- Some eMSPs also do a periodic re-pull (pull for safety net).
- When users start sessions, the CPO pushes Session updates (push).
- eMSP cross-references Tokens via pull (real-time query).
- Session completes; CPO pushes CDR (push with retry).
- eMSP pulls Tariffs occasionally to keep its rate cache fresh (pull-and-cache).
sequenceDiagram
participant CPO
participant eMSP
CPO->>eMSP: PUT Locations (push new)
eMSP-->>CPO: GET Locations (re-pull safety net)
CPO->>eMSP: PUT Session updates (push)
eMSP->>CPO: GET Token (real-time query)
CPO->>eMSP: POST CDR (push with retry)
eMSP-->>CPO: GET Tariffs (pull and cache)
A single connection between two parties involves all patterns.
Common pitfalls
Treating push as fire-and-forget. Without acknowledgment-based retry, push doesn’t guarantee delivery. Always wait for 200 OK and handle failures.
Polling when you should push. “I’ll just poll the Sessions endpoint every minute” misses fine-grained updates and burns bandwidth. Implement push instead.
Push without idempotency. Receiver gets duplicates; data is corrupted. Always idempotent.
Ignoring pagination on pull. First page is not the whole story.
Caching pulled data forever. Tariffs change; Locations get added; your cache goes stale. Define cache-invalidation rules.
The honest summary
OCPI’s pull/push pattern mix is intentional. Each module has data characteristics that justify its pattern choice. Implementations have to handle both. The biggest mistakes are treating push as fire-and-forget, ignoring idempotency, and polling when push is the better choice. Many of these show up repeatedly in real integrations — see common OCPI integration pitfalls (coming soon) for the field-tested list. Get the patterns right per module and your OCPI integration will be both performant and correct.