> ## 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

> Model a representative case: multiple vehicles, depots, and time windows, through to a usable result.

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}
{
  "resources": [
    {
      "id": "van-north",
      "vehicleProfile": { "type": "car" },
      "workingTimeWindow": { "begin": "2026-08-03T07:00:00Z", "end": "2026-08-03T16:00:00Z" },
      "departure": { "lon": 2.2950, "lat": 48.8738 },
      "arrival": { "lon": 2.2950, "lat": 48.8738 }
    },
    {
      "id": "van-south",
      "vehicleProfile": { "type": "car" },
      "workingTimeWindow": { "begin": "2026-08-03T07:00:00Z", "end": "2026-08-03T16:00:00Z" },
      "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.

## 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-03T09:00:00Z", "end": "2026-08-03T11:00:00Z" }
  ]
}
```

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"]
```

<Accordion title="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}
  {
    "objectives": ["maximizeMandatoryStops", "minimizeDelay"],
    "resources": [
      {
        "id": "van-north",
        "vehicleProfile": { "type": "car" },
        "workingTimeWindow": { "begin": "2026-08-03T07:00:00Z", "end": "2026-08-03T16:00:00Z" },
        "departure": { "lon": 2.2950, "lat": 48.8738 },
        "arrival": { "lon": 2.2950, "lat": 48.8738 }
      },
      {
        "id": "van-south",
        "vehicleProfile": { "type": "car" },
        "workingTimeWindow": { "begin": "2026-08-03T07:00:00Z", "end": "2026-08-03T16:00:00Z" },
        "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-03T09:00:00Z", "end": "2026-08-03T11:00:00Z" }] }] },
      { "id": "order-2", "stops": [{ "type": "single", "id": "stop-2", "position": { "lon": 2.3372, "lat": 48.8462 }, "kind": "delivery", "operationDuration": "PT10M", "preferredTimeWindows": [{ "begin": "2026-08-03T10:00:00Z", "end": "2026-08-03T12:00:00Z" }] }] },
      { "id": "order-3", "stops": [{ "type": "single", "id": "stop-3", "position": { "lon": 2.3708, "lat": 48.8330 }, "kind": "delivery", "operationDuration": "PT10M", "preferredTimeWindows": [{ "begin": "2026-08-03T13:00:00Z", "end": "2026-08-03T15:00:00Z" }] }] },
      { "id": "order-4", "stops": [{ "type": "single", "id": "stop-4", "position": { "lon": 2.2945, "lat": 48.8580 }, "kind": "delivery", "operationDuration": "PT10M", "preferredTimeWindows": [{ "begin": "2026-08-03T08:00:00Z", "end": "2026-08-03T10:00:00Z" }] }] }
    ]
  }
  ```
</Accordion>

## 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.** Datetimes without an explicit UTC offset are ambiguous — either include the offset on every datetime, or set the plan-level `tz` field once (for example, `"Europe/Paris"`) and write local, offset-free datetimes. Mixing both styles in the same plan is a common source of off-by-a-few-hours bugs.
* **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.