What it does
CANCEL_RESERVATION releases a reservation before its expiry, returning the connector to general availability. The CPO relays it as an OCPP CancelReservation.
It takes a single meaningful field — the reservation_id you supplied when you created the hold. This is why generating and persisting that ID at reservation time is not optional: without it, the only way a reservation ends is by expiring.
| Field | Required | Notes |
|---|---|---|
response_url | Yes | Where the CommandResult is POSTed |
reservation_id | Yes | The ID you supplied to RESERVE_NOW |
Worked example
POST /ocpi/2.2.1/commands/CANCEL_RESERVATION
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: 55e1c7d4
{
"response_url": "https://emsp.example.com/ocpi/2.2.1/commands/CANCEL_RESERVATION/55e1c7d4",
"reservation_id": "resv-2026-0811-4471"
}Acknowledgement and result:
HTTP/1.1 200 OK
{ "data": { "result": "ACCEPTED", "timeout": 30 }, "status_code": 1000,
"timestamp": "2026-08-11T18:31:09Z" }
POST /ocpi/2.2.1/commands/CANCEL_RESERVATION/55e1c7d4
{ "result": "CANCELED_RESERVATION" }Note the result value: CANCELED_RESERVATION, not ACCEPTED. This is the one command whose success value is specific to it, and hard-coding a check for ACCEPTED will make every successful cancellation look like a failure. It is a genuinely common bug.
UNKNOWN_RESERVATION is usually fine
The most frequent non-success result is UNKNOWN_RESERVATION. It almost never indicates a defect:
| Cause | Is it a problem? |
|---|---|
| The reservation already expired | No — the connector is free, which is what you wanted |
| The driver arrived and started charging | No — the reservation was consumed |
| The CPO cleared it during maintenance | No — outcome achieved |
You sent the wrong reservation_id | Yes — a real defect in your ID handling |
The distinguishing test is whether your own records show the reservation as still active. If your side believes it is live and the CPO does not, you have a state divergence worth investigating. If your side already knew it had lapsed, the response is confirming reality.
Why cancelling matters more than it looks
It is tempting to skip cancellation and let reservations expire. Each individual hold is only a few minutes of one connector. But the aggregate effect is what partners see:
- Abandoned reservations reduce network availability. A partner whose reservations routinely lapse rather than being cancelled is measurably degrading the CPO's utilisation, and CPOs track this.
- It is a common reason for reservation privileges being withdrawn. Since RESERVE_NOW is discretionary on the CPO's side, poor cancellation hygiene is a reason to turn it off for you.
- No-show fees depend on it. Where a CPO charges for unused reservations, cancelling promptly is what saves your customer money — and failing to cancel becomes a billing dispute you will lose.
Production lessons
- Check for
CANCELED_RESERVATION, notACCEPTED. The single most common implementation bug in this command. - Treat
UNKNOWN_RESERVATIONas success unless your records disagree. Only investigate when your state says the hold was still live. - Fire cancellation on session start. Once charging begins the hold is spent; release it rather than waiting for expiry.
- Make cancellation idempotent and retryable. It is safe to send twice, so retry on transport failure rather than giving up.
- Track your lapse rate per partner. The ratio of reservations cancelled to reservations expired is a metric CPOs will eventually ask you about.
- Never block a driver action on the cancel result. Fire it and move on; the driver should not wait on a housekeeping call.
Frequently asked questions
It releases a reservation created by RESERVE_NOW before its expiry, returning the connector to general availability. It takes only the reservation_id you supplied when creating the hold.
CANCELED_RESERVATION, not ACCEPTED. This is the only OCPI command with a success value specific to it, and code that checks for ACCEPTED will incorrectly treat every successful cancellation as a failure.
Usually not. It normally means the reservation already expired or was consumed when the driver started charging — in both cases the connector is free, which was the goal. It is only a defect if your own records still show the reservation as active.
Reservations that lapse instead of being cancelled reduce network availability, and CPOs track this. Since RESERVE_NOW support is discretionary, poor cancellation hygiene is a common reason for a CPO to withdraw reservation privileges. Where no-show fees apply, prompt cancellation is also what protects your customer from being charged.