The five commands
All five are sent by the eMSP (Sender) to the CPO (Receiver), and all five ultimately translate into an OCPP message to the charge point.
| Command | What it does | Underlying OCPP |
|---|---|---|
START_SESSION | Begin charging at a specific EVSE for a given token | RemoteStartTransaction / RequestStartTransaction |
STOP_SESSION | End an in-progress session by session ID | RemoteStopTransaction / RequestStopTransaction |
RESERVE_NOW | Hold an EVSE for a token until an expiry time | ReserveNow |
CANCEL_RESERVATION | Release a reservation early | CancelReservation |
UNLOCK_CONNECTOR | Physically release a latched cable | UnlockConnector |
NOT_SUPPORTED and design the driver-facing flow around that.The two-phase pattern
Every command follows the same shape:
- The eMSP
POSTs the command to the CPO, including aresponse_urlit controls. - The CPO replies immediately with a CommandResponse. This says only whether the command was accepted for processing, plus a timeout hint.
- The CPO forwards the command to the charger over OCPP and waits.
- When the charger responds — or fails to — the CPO
POSTs a CommandResult to theresponse_url.
The gap between step 2 and step 4 is typically 2 to 30 seconds, and can be much longer if the charger is on a poor cellular link. That latency is why the pattern exists: an HTTP request cannot be held open for the round trip to a piece of roadside hardware.
CommandResponse — accepted for processing
| Value | Meaning |
|---|---|
ACCEPTED | Queued and forwarded. A CommandResult will follow. |
REJECTED | Not processed. No result will follow. |
NOT_SUPPORTED | This CPO does not implement this command. |
UNKNOWN_SESSION | For STOP_SESSION — the session ID is not recognised. |
CommandResult — what actually happened
| Value | Meaning |
|---|---|
ACCEPTED | The charger carried out the command. |
REJECTED | The charger refused it. |
CANCELED_RESERVATION | Reservation successfully cancelled. |
EVSE_OCCUPIED | Another vehicle is already using it. |
EVSE_INOPERATIVE | The EVSE is faulted or unavailable. |
FAILED | The command was attempted and failed. |
NOT_SUPPORTED | The charger does not support it. |
TIMEOUT | The charger never answered. |
UNKNOWN_RESERVATION | The reservation ID is not recognised. |
Worked example: START_SESSION end to end
The eMSP asks the CPO to start a session for one of its customers at a specific EVSE:
POST /ocpi/2.2.1/commands/START_SESSION
Authorization: Token <base64 token>
OCPI-from-country-code: IN
OCPI-from-party-id: EMS
OCPI-to-country-code: IN
OCPI-to-party-id: EFI
X-Request-ID: 7c1a4e90
X-Correlation-ID: booking-88231
{
"response_url": "https://emsp.example.com/ocpi/2.2.1/commands/START_SESSION/7c1a4e90",
"token": {
"country_code": "IN", "party_id": "EMS",
"uid": "DR-88231", "type": "APP_USER",
"contract_id": "IN-EMS-C0142",
"issuer": "Example Mobility Services",
"valid": true, "whitelist": "ALLOWED",
"last_updated": "2026-08-11T08:55:00Z"
},
"location_id": "LOC-ND-014",
"evse_uid": "EVSE-014-02",
"authorization_reference": "auth-4471"
}The CPO answers immediately — this is not the outcome:
HTTP/1.1 200 OK
{
"data": { "result": "ACCEPTED", "timeout": 30 },
"status_code": 1000,
"timestamp": "2026-08-11T09:12:01Z"
}Eleven seconds later, after the charger has answered over OCPP, the CPO posts the real result to the callback URL:
POST /ocpi/2.2.1/commands/START_SESSION/7c1a4e90
Authorization: Token <base64 token>
X-Correlation-ID: booking-88231
{
"result": "ACCEPTED"
}Only now can the eMSP tell the driver charging has begun. A Session object will appear separately through the Sessions module.
The timeout field, and what to do when it passes
timeout in the CommandResponse is the number of seconds the CPO expects to need. It is a hint, not a contract, and the spec does not define what happens if it elapses without a result.
The behaviour that works in production is:
- Start a timer at
timeoutplus a margin — roughly 1.5× is a reasonable default. - On expiry, mark the command indeterminate, not failed. The charger may still be starting.
- Reconcile against the Sessions module rather than guessing. If a Session appears for that EVSE and token, the command succeeded regardless of whether the result ever arrived.
- Never auto-retry
START_SESSIONon timeout. You risk two sessions and a very unhappy driver.
response_url handler must accept and correctly process a result for a command you have already given up on — and must be idempotent, because retries mean you will occasionally receive the same result twice.Correlating commands with sessions
The hardest part of this module is not sending the command — it is knowing which resulting Session belongs to which command. OCPI gives you one tool for this: authorization_reference.
If you set it on the command, a conforming CPO echoes it on the resulting Session and CDR. That gives you an end-to-end join key from the driver's tap through to the invoice. Without it you are matching on token, EVSE and timestamp proximity, which is fragile at any real volume.
Not every CPO propagates it reliably. Verify it during partner certification rather than discovering the gap during reconciliation.
Production lessons
- Make
response_urlunique per command. Embedding the request ID in the path, as in the example above, means the callback identifies itself without you having to parse a body to route it. - Make the callback handler idempotent. Duplicate results happen. Key on the command ID and ignore repeats.
- Never present the CommandResponse to the user.
ACCEPTEDat phase one means “we are trying”. Showing “charging started” at that point produces support tickets when the charger then reportsEVSE_OCCUPIED. - Set
authorization_referenceon every command. It costs nothing and it is the only clean correlation key you get. - Treat
NOT_SUPPORTEDas a partner capability, not an error. Record it against the partner and stop offering that action in the UI for their locations. - Log both phases against one correlation ID. Commands are the most common source of partner disputes, and a single ID spanning request, response and result is what resolves them.
Frequently asked questions
Five: START_SESSION, STOP_SESSION, RESERVE_NOW, CANCEL_RESERVATION and UNLOCK_CONNECTOR. All are sent by the eMSP to the CPO and translate into OCPP messages to the charge point.
Because the CPO must relay the command to physical roadside hardware over OCPP and wait for it to answer, which can take seconds to minutes. An HTTP request cannot be held open that long, so OCPI splits it: an immediate CommandResponse acknowledging receipt, then a CommandResult POSTed later to a callback URL.
CommandResponse is the synchronous reply and says only whether the command was accepted for processing. CommandResult is the asynchronous callback that reports what the charger actually did. Only CommandResult is the outcome.
The spec does not define it. In practice you mark the command indeterminate rather than failed, reconcile against the Sessions module to see whether a session actually started, and never auto-retry START_SESSION — retrying risks starting two sessions.
Set authorization_reference on the command. A conforming CPO echoes it on the resulting Session and CDR, giving you an end-to-end join key from the driver's tap to the invoice.