OCPI COMMAND · UPDATED 11 AUG 2026

CANCEL_RESERVATION: releasing a hold

The smallest command in OCPI, and the one most often left unimplemented. Cancelling a reservation costs you nothing and gives a connector back to the network — which is exactly why partners notice when you skip it.

PART OF THE OCPI 2.2.1 COMPLETE GUIDE · 15 MODULE & COMMAND GUIDES

IN ONE PARAGRAPH

CANCEL_RESERVATION releases a hold created by RESERVE_NOW, using the reservation_id you generated. It takes one field, follows the same async pattern as every other command, and its most common result — UNKNOWN_RESERVATION — usually means the reservation already expired rather than that anything went wrong.

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.

FieldRequiredNotes
response_urlYesWhere the CommandResult is POSTed
reservation_idYesThe 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:

CauseIs it a problem?
The reservation already expiredNo — the connector is free, which is what you wanted
The driver arrived and started chargingNo — the reservation was consumed
The CPO cleared it during maintenanceNo — outcome achieved
You sent the wrong reservation_idYes — 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.
Cancel on every terminal path. The driver cancels in-app; the driver starts charging; the driver's ETA slips past expiry; the trip is abandoned. Every one of those should fire a cancellation. Wiring it only to the explicit “cancel” button covers a minority of real cases.

Production lessons

  • Check for CANCELED_RESERVATION, not ACCEPTED. The single most common implementation bug in this command.
  • Treat UNKNOWN_RESERVATION as 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

What does OCPI CANCEL_RESERVATION do?

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.

What result does a successful CANCEL_RESERVATION return?

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.

Does UNKNOWN_RESERVATION mean something went wrong?

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.

Why is cancelling reservations important?

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.