# Multi-stop deliveries

> Send one courier to several places in a fixed order: up to 10 stops between pickup and dropoff, each with its own contact and instructions.

A multi-stop delivery is one courier trip with intermediate stops between `pickup` and `dropoff`, for example a supplier on the way, or several recipients on one route.

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

## Rules

- Send up to 10 entries in `stops`, in the order the courier should drive them.
- Do not repeat `pickup` or `dropoff` inside `stops`.
- Every stop has `address`, `latitude`, and `longitude`. `contact` and `instructions` are optional per stop. They are required at pickup (`contact`) and dropoff (`contact`).
- The price covers the whole route, so quote with the same stops you book.

## Quote and book a route with stops

**1. Quote the route**

Send the stops to [`POST /v1/quotes`](https://api.maxmove.com/v1/docs#tag/quotes/POST/v1/quotes). Quotes take bare locations, without contacts or instructions.

```bash cURL
curl -X POST https://api.maxmove.com/v1/quotes \
  -H "x-api-key: $MAXMOVE_KEY" -H "content-type: application/json" \
  -d '{
    "pickup":  { "address": "Ehrenstraße 15, 50672 Köln", "latitude": 50.9385, "longitude": 6.9469 },
    "stops": [
      { "address": "Friedrich-Ebert-Platz 2, 51373 Leverkusen", "latitude": 51.0327, "longitude": 6.9877 }
    ],
    "dropoff": { "address": "Königsallee 27, 40212 Düsseldorf", "latitude": 51.2233, "longitude": 6.7768 },
    "vehicle_type_id": "courier"
  }'
```

```ts Node.js
const pickup = { address: 'Ehrenstraße 15, 50672 Köln', latitude: 50.9385, longitude: 6.9469 };
const stop = { address: 'Friedrich-Ebert-Platz 2, 51373 Leverkusen', latitude: 51.0327, longitude: 6.9877 };
const dropoff = { address: 'Königsallee 27, 40212 Düsseldorf', latitude: 51.2233, longitude: 6.7768 };

const quote = await fetch(`${BASE_URL}/quotes`, {
  method: 'POST',
  headers,
  body: JSON.stringify({ pickup, stops: [stop], dropoff, vehicle_type_id: 'courier' }),
}).then((res) => res.json());
```

```python Python
pickup = {"address": "Ehrenstraße 15, 50672 Köln", "latitude": 50.9385, "longitude": 6.9469}
stop = {"address": "Friedrich-Ebert-Platz 2, 51373 Leverkusen", "latitude": 51.0327, "longitude": 6.9877}
dropoff = {"address": "Königsallee 27, 40212 Düsseldorf", "latitude": 51.2233, "longitude": 6.7768}

quote = requests.post(
    f"{BASE_URL}/quotes",
    headers=headers,
    json={"pickup": pickup, "stops": [stop], "dropoff": dropoff, "vehicle_type_id": "courier"},
).json()
```

**2. Book the same route with handoff details**

Send the identical route to [`POST /v1/deliveries`](https://api.maxmove.com/v1/docs#tag/deliveries/POST/v1/deliveries) and add who the courier meets where. With `quote_id`, any difference in addresses, coordinates, or stop order fails with `400 quote_mismatch`.

```ts Node.js
const delivery = await fetch(`${BASE_URL}/deliveries`, {
  method: 'POST',
  headers: { ...headers, 'Idempotency-Key': 'order-4711' },
  body: JSON.stringify({
    quote_id: quote.id,
    pickup: { ...pickup, contact: { name: 'Feinkost Ehrenfeld', phone: '+49 221 1234567' } },
    stops: [{ ...stop, instructions: 'Collect two crates at gate 3.' }],
    dropoff: {
      ...dropoff,
      contact: { name: 'Erika Muster', phone: '+49 170 1234567' },
      instructions: 'Ring at reception, 2nd floor.',
    },
    vehicle_type_id: 'courier',
  }),
}).then((res) => res.json());
```

```python Python
delivery = requests.post(
    f"{BASE_URL}/deliveries",
    headers={**headers, "Idempotency-Key": "order-4711"},
    json={
        "quote_id": quote["id"],
        "pickup": {**pickup, "contact": {"name": "Feinkost Ehrenfeld", "phone": "+49 221 1234567"}},
        "stops": [{**stop, "instructions": "Collect two crates at gate 3."}],
        "dropoff": {
            **dropoff,
            "contact": {"name": "Erika Muster", "phone": "+49 170 1234567"},
            "instructions": "Ring at reception, 2nd floor.",
        },
        "vehicle_type_id": "courier",
    },
).json()
```

## What changes while the courier drives

- The delivery returns `stops` in the order you sent them, each with its `contact` and `instructions` (or `null`).
- `delivery.courier_arrived` fires at every waypoint. At pickup and dropoff the status becomes `at_pickup` or `at_dropoff`; at an intermediate stop the status does not change.
- [Tracking](https://maxmove.com/en/developers/docs/guides/tracking) reports `eta_seconds` and `distance_meters` to the next waypoint, not to the final dropoff.

> **Tip:**
> Several independent recipients with different time windows are usually better served by separate deliveries: each one then has its own status, tracking link, and proof of delivery.

---

Source: https://maxmove.com/en/developers/docs/guides/multi-stop
