# Cancellations and failed deliveries

> Cancel a delivery in time, handle deliveries that Maxmove cancels or that find no courier, and test every failure path.

A delivery ends as `delivered` or `cancelled`. Cancelled covers three cases: you cancelled it, no courier accepted it, or Maxmove ended it.

Code examples use `BASE_URL` and `headers` from the [Quickstart](https://maxmove.com/en/developers/docs/quickstart).

## Cancel a delivery

Call [`POST /v1/deliveries/{deliveryId}/cancel`](https://api.maxmove.com/v1/docs#tag/deliveries/POST/v1/deliveries/{deliveryId}/cancel) while the status is `pending`, `courier_assigned`, or `at_pickup`. The response is the delivery with status `cancelled`, and Maxmove sends `delivery.cancelled`.

Once the courier has picked up the goods, or the delivery has already ended, the request fails with `409 not_cancellable`. Contact Maxmove support in that case.

```ts Node.js
const res = await fetch(`${BASE_URL}/deliveries/${deliveryId}/cancel`, { method: 'POST', headers });
if (res.status === 409) {
  const { error } = await res.json();
  if (error.code === 'not_cancellable') {
    // Too late: the goods are on their way. Escalate to support.
  }
}
```

```python Python
res = requests.post(f"{BASE_URL}/deliveries/{delivery_id}/cancel", headers=headers)
if res.status_code == 409 and res.json()["error"]["code"] == "not_cancellable":
    pass  # Too late: the goods are on their way. Escalate to support.
```

```php PHP
$response = $client->post("deliveries/{$deliveryId}/cancel", ['http_errors' => false]);
if ($response->getStatusCode() === 409) {
    $error = json_decode((string) $response->getBody(), true)['error'];
    if ($error['code'] === 'not_cancellable') {
        // Too late: the goods are on their way. Escalate to support.
    }
}
```

## When Maxmove ends a delivery

| What happened | Status | Event | What to do |
| --- | --- | --- | --- |
| No courier accepted the delivery in time | `cancelled` | `delivery.cancelled` | Book again, possibly later or with another vehicle type. |
| Maxmove or the courier ended the delivery, for example because the handover failed | `cancelled` | `delivery.cancelled` | Tell your customer and book again if the goods still need to go. |

Booking again means a new delivery: use a new `Idempotency-Key`, because the old key returns the old, ended delivery. A new `quote_id` is needed if the old quote has expired.

## Test every path

With a test key, `test_scenario` on [`POST /v1/deliveries`](https://api.maxmove.com/v1/docs#tag/deliveries/POST/v1/deliveries) makes the Robocourier fail on purpose:

| `test_scenario` | What happens |
| --- | --- |
| `courier_cancelled` | The courier accepts, then the delivery is cancelled before pickup. |
| `delivery_failed` | The courier reaches the dropoff, then the delivery is cancelled. |

Cancel a test delivery yourself to test your cancel flow, both before pickup and after (expect `409 not_cancellable`).

---

Source: https://maxmove.com/en/developers/docs/guides/cancellations
