> ## Documentation Index
> Fetch the complete documentation index at: https://developers.kardinal.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Complete route walkthrough

> Build a realistic plan step by step, with two vans, two depots, and delivery time windows, then read the result and avoid the common pitfalls at this stage.

This walkthrough builds a more realistic plan than [First API call](/getting-started/first-api-call): two vehicles starting from two different depots, delivering to stops with time windows. It assumes you've already authenticated — see [Authentication](/guides/authentication) if you haven't.

## The scenario

Two vans, each starting and ending its day at a different depot, deliver to four stops with client-facing delivery windows.

## Step 1 — start minimal: resources and orders only

Get a feasible plan running before adding any constraints. Each resource needs at minimum an `id`, a `vehicleProfile`, and a `workingTimeWindow`; each order needs an `id` and at least one `stop` with a `position`.

```json theme={null}
{
  "tz": "Europe/Paris",
  "resources": [
    {
      "id": "van-north",
      "vehicleProfile": { "type": "car" },
      "workingTimeWindow": { "begin": "2026-08-03 07:00", "end": "2026-08-03 16:00" },
      "departure": { "lon": 2.2950, "lat": 48.8738 },
      "arrival": { "lon": 2.2950, "lat": 48.8738 }
    },
    {
      "id": "van-south",
      "vehicleProfile": { "type": "car" },
      "workingTimeWindow": { "begin": "2026-08-03 07:00", "end": "2026-08-03 16:00" },
      "departure": { "lon": 2.3510, "lat": 48.8228 },
      "arrival": { "lon": 2.3510, "lat": 48.8228 }
    }
  ],
  "orders": [
    { "id": "order-1", "stops": [{ "type": "single", "id": "stop-1", "position": { "lon": 2.3212, "lat": 48.8656 }, "kind": "delivery", "operationDuration": "PT10M" }] },
    { "id": "order-2", "stops": [{ "type": "single", "id": "stop-2", "position": { "lon": 2.3372, "lat": 48.8462 }, "kind": "delivery", "operationDuration": "PT10M" }] },
    { "id": "order-3", "stops": [{ "type": "single", "id": "stop-3", "position": { "lon": 2.3708, "lat": 48.8330 }, "kind": "delivery", "operationDuration": "PT10M" }] },
    { "id": "order-4", "stops": [{ "type": "single", "id": "stop-4", "position": { "lon": 2.2945, "lat": 48.8580 }, "kind": "delivery", "operationDuration": "PT10M" }] }
  ]
}
```

Submit this the same way as in [First API call](/getting-started/first-api-call) — `POST /plans` (the service generates the plan's `id`) — and confirm that all four stops are planned. With the default objectives, which include `minimizeResources`, expect a single tour: one van can serve all four stops within its working day, so the engine leaves the other one unused. Positions here are illustrative — see [Positions and geocoding](/reference/data-model#positions-and-geocoding) for why real positions must be pre-geocoded before you submit them.

> [!TIP]
>
> With the Python or TypeScript SDK, save each step's payload as a file and [load it into the SDK's model](/sdks/building-plans#loading-an-existing-json-payload): a misspelled field is then refused locally, before you submit it.

## Step 2 — add depots as `departure`/`arrival`

The preceding plan already has each van start and end at a depot position rather than at the first/last stop — this is what makes the two-depot scenario realistic. If `departure`/`arrival` are omitted, the working day starts and ends at whichever stop the engine happens to assign first/last, which is rarely what a dispatcher expects for a fleet with fixed depots.

## Step 3 — add delivery time windows, and choose hard vs soft deliberately

Now add a delivery window to each stop. This is the step where it's easy to get the modeling choice wrong: a client-facing or "contractual" window is not automatically a hard constraint. Ask what should happen if the fleet can't hit the window exactly:

* If a late visit is still worth making — the customer would rather get a delayed delivery than none — use **`preferredTimeWindows`**. Missing it costs `delay`, tracked by the `minimizeDelay` objective, but the stop still gets served.
* Only use **`authorizedTimeWindows`** if a visit outside the window genuinely can't happen (site closed, access refused). See [Hard vs soft constraints](/concepts/hard-vs-soft-constraints#a-contractual-window-is-not-automatically-a-hard-one) for the full reasoning — the short version is: default to `preferredTimeWindows` for contractual windows unless you've explicitly confirmed otherwise with the business.

```json theme={null}
{
  "type": "single",
  "id": "stop-1",
  "position": { "lon": 2.3212, "lat": 48.8656 },
  "kind": "delivery",
  "operationDuration": "PT10M",
  "preferredTimeWindows": [
    { "begin": "2026-08-03 09:00", "end": "2026-08-03 11:00" }
  ]
}
```

If you leave `objectives` out, the default list already includes `minimizeDelay` — see [The default list](/concepts/objectives#the-default-list). If you set your own list, keep `maximizeMandatoryStops` first: a list with only `minimizeDelay` gives the engine no reason to plan any stop at all.

```json theme={null}
"objectives": ["maximizeMandatoryStops", "minimizeDelay"]
```

> **Full plan for this step**
>
> The plan from step 1 with a preferred window on each stop and the two-objective list. With only these two objectives, nothing asks the engine to save a van, so it may spread the stops over both.
>
> ```json theme={null}
> {
>   "tz": "Europe/Paris",
>   "objectives": ["maximizeMandatoryStops", "minimizeDelay"],
>   "resources": [
>     {
>       "id": "van-north",
>       "vehicleProfile": { "type": "car" },
>       "workingTimeWindow": { "begin": "2026-08-03 07:00", "end": "2026-08-03 16:00" },
>       "departure": { "lon": 2.2950, "lat": 48.8738 },
>       "arrival": { "lon": 2.2950, "lat": 48.8738 }
>     },
>     {
>       "id": "van-south",
>       "vehicleProfile": { "type": "car" },
>       "workingTimeWindow": { "begin": "2026-08-03 07:00", "end": "2026-08-03 16:00" },
>       "departure": { "lon": 2.3510, "lat": 48.8228 },
>       "arrival": { "lon": 2.3510, "lat": 48.8228 }
>     }
>   ],
>   "orders": [
>     { "id": "order-1", "stops": [{ "type": "single", "id": "stop-1", "position": { "lon": 2.3212, "lat": 48.8656 }, "kind": "delivery", "operationDuration": "PT10M", "preferredTimeWindows": [{ "begin": "2026-08-03 09:00", "end": "2026-08-03 11:00" }] }] },
>     { "id": "order-2", "stops": [{ "type": "single", "id": "stop-2", "position": { "lon": 2.3372, "lat": 48.8462 }, "kind": "delivery", "operationDuration": "PT10M", "preferredTimeWindows": [{ "begin": "2026-08-03 10:00", "end": "2026-08-03 12:00" }] }] },
>     { "id": "order-3", "stops": [{ "type": "single", "id": "stop-3", "position": { "lon": 2.3708, "lat": 48.8330 }, "kind": "delivery", "operationDuration": "PT10M", "preferredTimeWindows": [{ "begin": "2026-08-03 13:00", "end": "2026-08-03 15:00" }] }] },
>     { "id": "order-4", "stops": [{ "type": "single", "id": "stop-4", "position": { "lon": 2.2945, "lat": 48.8580 }, "kind": "delivery", "operationDuration": "PT10M", "preferredTimeWindows": [{ "begin": "2026-08-03 08:00", "end": "2026-08-03 10:00" }] }] }
>   ]
> }
> ```

## Step 4 — read the result

Fetch the solution the same way as in [First API call](/getting-started/first-api-call) and check, per tour:

* `tours[].wayPoints` — the assigned stops, in visiting sequence, each with a computed `arrivalTime`.
* `tours[].isValid` — whether that specific tour respects all hard constraints; check this per-tour, not just at the plan level.
* `unaffectedStopIds` — any stop that couldn't be placed at all. With everything modeled as `preferredTimeWindows` earlier, this should be empty; if you'd used `authorizedTimeWindows` instead and a window were unreachable, the stop would show up here instead of arriving late.
* `tours[].distanceInKm` and `tours[].workingDuration`, useful for a sanity check against what you'd expect for the geography.

If a stop you expected to be served ends up in `unaffectedStopIds`, see [Handling infeasibility](/guides/handling-infeasibility) for how to diagnose which constraint caused it.

## Common pitfalls at this stage

* **Units.** Distances in the API are kilometers, not miles; durations are ISO 8601 (`PT30M`, not `30`); make sure any values sourced from a spreadsheet are converted before submission.
* **Time zones.** A datetime ends with `Z` (UTC), carries an offset such as `+02:00`, or is a local datetime written with a space (`2026-10-20 10:00`) that the API reads in the plan-level `tz` field (for example, `"Europe/Paris"`). A local datetime without `tz` is rejected, and a `Z` or an offset always wins over `tz`. Set `tz` in every plan, because it also decides the offset the API uses when it writes datetimes back. See [Dates and time zones](/reference/data-model#dates-and-time-zones).
* **Positions.** Every `position` (stops, and a resource's `departure`/`arrival`) must be a `{lat, lon}` pair in decimal degrees: the API does not geocode addresses. Geocode them on your side before building the plan, or contact [customer.success@kardinal.ai](mailto:customer.success@kardinal.ai) to have an address set geocoded on request. Spot-check the result, because a wrongly geocoded position raises no error: it silently routes to the wrong place. See [Positions and geocoding](/reference/data-model#positions-and-geocoding).
* **Hard vs soft defaults.** As shown in Step 3, don't default a contractual time window to `authorizedTimeWindows` just because it's contractual — that choice silently drops stops rather than delivering them late.

## See also

* [Data model](/reference/data-model) — an overview of everything used earlier, each part linking to its schema in the API reference.
* [Hard vs soft constraints](/concepts/hard-vs-soft-constraints) — the general reasoning behind the Step 3 choice.
* [Handling infeasibility](/guides/handling-infeasibility) — what to do when a stop doesn't get planned.
* [Modeling advanced constraints](/guides/advanced-constraints) — the next step once a two-vehicle, time-windowed plan is working: capacities, skills, breaks, and multiple time windows.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.