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

# Orders and sequences

> Model how stops are linked: the stops of one order, optional orders, stops visited back to back, a time limit between the first and last stop, one site chosen among several, stops kept on one vehicle in any order, and how to check the solution.

A stop is one visit, at one position. An order groups stops that belong together. A pickup and its delivery are an example. The engine plans all the stops of an order on one resource. A resource is a vehicle with its driver. A tour is the route of one resource, from its start to its end. In the solution, a tour is a list of waypoints, and a waypoint is one line of that list. This guide covers the links between stops. Each section describes one link between stops, with a link to the API reference where the exact behavior is written. The last section shows how to check the solution.

These snippets use local times where they have times. The plan you put them in needs `"tz"`. See [Dates and time zones](https://developers.kardinal.ai/reference/data-model.md#dates-and-time-zones).

## Stops of one order

The `stops` array of an order is a sequence. The engine plans these stops on one resource, in the order of the array. A pickup then a delivery in one order carry the same goods. The resource loads them at the first stop and unloads them at the second.

```json theme={null}
{
  "id": "order-1",
  "stops": [
    { "type": "single", "id": "pickup-1", "kind": "pickup", "position": { "lon": 2.364, "lat": 48.85 }, "operationDuration": "PT5M" },
    { "type": "single", "id": "delivery-1", "kind": "delivery", "position": { "lon": 2.487, "lat": 48.85 }, "operationDuration": "PT5M" }
  ]
}
```

The rules are these:

* **One vehicle, array order.** In the sandbox, an order with a far stop and then a near stop was planned in that order. The same two stops in two separate orders were planned near first. With two vans 20 km apart, an order with one stop near each van went to a single van.
* **All stops or none.** If one stop of an order can't be served, the engine leaves the whole order out. The stop that fits is not planned alone. In the sandbox, an order with a second stop that opened after the working day of the van went to `unaffectedStopIds` with both stops, and the solution gave no violation.
* **A limit of 4 stops by default.** An order without `successiveStops` can have at most 4 stops. In the sandbox, an order with 5 stops was refused with a `403` error. See [Limits and quotas](https://developers.kardinal.ai/reference/limits-and-quotas.md) for the error and for how to raise the limit.

See [`Order`](https://developers.kardinal.ai/api-reference/plan/create-a-plan#body-orders) for the rule, and [Order, stop](https://developers.kardinal.ai/reference/data-model.md#order-stop) for the loads of a pickup and a delivery.

## Optional orders

An order with `optional: true` is one that the engine can leave out. It leaves the `maximizeMandatoryStops` objective, so leaving it out costs nothing there. Without `maximizeOptionalStops` in the objectives, the engine has no reason to plan it, so it is unlikely to be planned. The default list of objectives contains `maximizeOptionalStops`.

```json theme={null}
{ "id": "order-bonus", "optional": true, "stops": [ ... ] }
```

If you write your own `objectives` list, add `maximizeOptionalStops` yourself. In the sandbox, a plan with one mandatory and one optional order planned both with the default list. With a list that left out `maximizeOptionalStops`, the optional stop went to `unaffectedStopIds`. See [`maximizeOptionalStops`](https://developers.kardinal.ai/concepts/objectives#maximizeoptionalstops) for the objective.

An optional order is not the same as an order with a low `priority`. A mandatory order, however low its priority, still counts in `maximizeMandatoryStops`.

See [`Order.optional`](https://developers.kardinal.ai/api-reference/plan/create-a-plan#body-orders) for the rule.

## Stops back to back

`successiveStops: true` keeps the stops of one order consecutive. The vehicle visits them one after the other, with no stop of another order in between. Without it, the engine can interleave the stops of two orders.

```json theme={null}
{ "id": "order-1", "successiveStops": true, "stops": [ ... ] }
```

In the sandbox, two orders each had a pickup near the depot and a delivery far from it. Without `successiveStops`, the engine planned both pickups, then both deliveries. With `successiveStops` on both orders, it planned the first pickup and its delivery, then the second pair. The flag applies to each order on its own. With the flag on one order only, that order stayed consecutive and the other order was planned around it.

A tour that breaks the rule reports a violation of type `successiveStops`.

The API reference calls `successiveStops` and `maxStopSpan` mutually exclusive, so use one or the other on an order. The API doesn't refuse a plan that sets both. See [`Order.successiveStops`](https://developers.kardinal.ai/api-reference/plan/create-a-plan#body-orders) for the rule.

## Time from first to last stop

`maxStopSpan` limits the time that an order takes, without forcing its stops to be consecutive. It is a duration such as `PT4H`. The span runs from the end of service at the first stop of the order to the start of service at its last stop. Waiting time counts.

```json theme={null}
{ "id": "order-1", "maxStopSpan": "PT4H", "stops": [ ... ] }
```

The span covers the whole order, not each pair of stops. In the sandbox, an order had stops at about 08:01, 10:00 and 12:00. A span of 2 h 30 left the order out, although each gap was under 2 hours. A span of 4 h 30 planned it. Another test gave the first stop a service of 60 minutes. It showed that the span starts at the end of that service, not at its start.

The engine doesn't delay the first stop to make the order fit the span. In the sandbox and in production, an order had a pickup window from 08:00 to 12:00, a delivery at 12:00 and a span of 1 hour. The engine left the order out, although the pickup could have waited until 11:00. The solution gave no violation. In the sandbox, with a pickup window from 11:00 to 12:00 and the same span of 1 hour, the order was planned. The van waited until 11:00.

So you need to narrow the window of the first stop yourself.

See [`Order.maxStopSpan`](https://developers.kardinal.ai/api-reference/plan/create-a-plan#body-orders) for the rule.

## One site among several

An `AlternativesStop` is a stop with several candidates. A candidate is a single stop. The engine serves exactly one candidate when it plans the order. A waste collection with several disposal sites is an example. The engine picks the site that fits the tour.

```json theme={null}
{
  "id": "order-3",
  "stops": [
    { "type": "single", "id": "collect-3", "kind": "pickup", "position": { "lon": 2.309, "lat": 48.85 }, "operationDuration": "PT5M" },
    {
      "type": "alternatives",
      "id": "drop-site",
      "alternatives": [
        { "type": "single", "id": "drop-far", "kind": "delivery", "position": { "lon": 2.145, "lat": 48.85 }, "operationDuration": "PT5M" },
        { "type": "single", "id": "drop-near", "kind": "delivery", "position": { "lon": 2.282, "lat": 48.85 }, "operationDuration": "PT5M" }
      ]
    }
  ]
}
```

The rules are these:

* **One candidate is served.** In the sandbox, an order with three candidate sites planned the nearest one. When the nearest site was closed, the engine planned another candidate. An order made only of an alternatives group was planned too.
* **All candidates have the same `kind`.** A candidate without `kind` counts as a `delivery`. A group that mixes kinds is refused with a `400` error.
* **The tour names the candidate, never the group.** A waypoint carries the id of the chosen candidate, here `drop-near`. The id of the group, here `drop-site`, appears nowhere in the solution.
* **The other candidates are listed apart.** The candidates that were not chosen are in `unaffectedAlternativeIds`, not in `unaffectedStopIds`.
* **If no candidate fits, the whole order is left out.** The id of one candidate appears in `unaffectedStopIds`, and the ids of the others appear in `unaffectedAlternativeIds`. The API reference doesn't say which candidate goes in which list. Don't write code that depends on it.

An `AlternativesStop` can switch the engine to a slower algorithm. See [Sizing `maxOptimizationDuration` for a large problem](https://developers.kardinal.ai/guides/handling-large-volumes#sizing-maxoptimizationduration-for-a-large-problem). Use it for a choice between sites. If you only want a stop that the engine can leave out, use an [optional order](#optional-orders).

This guide shows no "only if needed" pattern, such as a stop that the vehicle visits only if it doesn't already carry an item. A group that mixes kinds is refused, and a candidate with no `kind` counts as a `delivery`.

See [`AlternativesStop`](https://developers.kardinal.ai/api-reference/plan/create-a-plan#body-orders) for the rule. The [Waste collection](https://developers.kardinal.ai/case-studies/waste-collection.md) case study uses a group of three disposal sites.

## Same vehicle in any order

An order with several stops already keeps them on one vehicle, but in the order of the array. Sometimes you need the opposite: a set of stops that must share one vehicle, while the vehicle picks the best order. A set of key-gated deliveries is the usual case, or the stops of one customer that a single driver should handle. A `maxCumulatedCost` entry in `globalConstraints` does this. It counts how many vehicles serve the set, and it allows one.

The plan below has two vans tagged `driver`, and three orders with one stop each, all tagged `keyset-1`. Two stops are close to `van-1` and one is close to `van-2`, so without the cap the stops would be split between the two vans. The plan carries `"tz"` because it is a whole plan, and it can be sent as it is to [`POST /plans`](https://developers.kardinal.ai/api-reference/plan/create-a-plan).

```json theme={null}
{
  "tz": "Europe/Paris",
  "resources": [
    {
      "id": "van-1",
      "vehicleProfile": {
        "type": "car"
      },
      "workingTimeWindow": {
        "begin": "2026-10-22 06:00",
        "end": "2026-10-22 20:00"
      },
      "departure": {
        "lon": 2.35,
        "lat": 48.85
      },
      "arrival": {
        "lon": 2.35,
        "lat": 48.85
      },
      "tags": [
        "driver"
      ]
    },
    {
      "id": "van-2",
      "vehicleProfile": {
        "type": "car"
      },
      "workingTimeWindow": {
        "begin": "2026-10-22 06:00",
        "end": "2026-10-22 20:00"
      },
      "departure": {
        "lon": 2.55,
        "lat": 48.95
      },
      "arrival": {
        "lon": 2.55,
        "lat": 48.95
      },
      "tags": [
        "driver"
      ]
    }
  ],
  "orders": [
    {
      "id": "order-1",
      "stops": [
        {
          "type": "single",
          "id": "stop-1",
          "position": {
            "lon": 2.36,
            "lat": 48.86
          },
          "kind": "delivery",
          "operationDuration": "PT10M",
          "tags": [
            "keyset-1"
          ]
        }
      ]
    },
    {
      "id": "order-2",
      "stops": [
        {
          "type": "single",
          "id": "stop-2",
          "position": {
            "lon": 2.364,
            "lat": 48.862
          },
          "kind": "delivery",
          "operationDuration": "PT10M",
          "tags": [
            "keyset-1"
          ]
        }
      ]
    },
    {
      "id": "order-3",
      "stops": [
        {
          "type": "single",
          "id": "stop-3",
          "position": {
            "lon": 2.55,
            "lat": 48.96
          },
          "kind": "delivery",
          "operationDuration": "PT10M",
          "tags": [
            "keyset-1"
          ]
        }
      ]
    }
  ],
  "globalConstraints": [
    {
      "type": "maxCumulatedCost",
      "name": "one-vehicle-per-keyset-1",
      "maximum": 1,
      "costsByResourceTag": {
        "driver": {
          "costsByStopTag": {
            "keyset-1": {
              "costFloor": 0,
              "costCoeff": 1,
              "overcostFloor": 1,
              "overcostCoeff": 0
            }
          }
        }
      }
    }
  ],
  "objectives": [
    "maximizeMandatoryStops",
    "minimizeDistance"
  ]
}
```

Here is how to read the `maxCumulatedCost` entry. `costCoeff: 1` counts one for each stop of the set that a vehicle visits. `overcostFloor: 1` with `overcostCoeff: 0` stops the count at 1, so a vehicle that takes one stop of the set costs 1, and a vehicle that takes all three also costs 1. Each vehicle that takes at least one stop of the set therefore costs 1, and `maximum: 1` across the fleet leaves room for one vehicle only. On the sandbox, this plan puts all three stops on one van and leaves `unaffectedStopIds` empty. The same plan without the `globalConstraints` entry splits the set across both vans.

A few traps are worth knowing before you use this pattern:

* Every resource that may serve the set must carry the tag in `costsByResourceTag`, here `driver`. The cap only counts resources that carry the tag, and the wildcard `"*"` is refused. A resource without the tag could take stops of the set freely.
* Write one entry per set, and give each set its own stop tag. Two sets that share a tag would be counted as one set.
* If no single vehicle can serve the whole set, for example because of time windows or capacity, the extra stops are not given to a second vehicle. They stay in `unaffectedStopIds`, and `globalViolations` stays empty. See [Capping a fleet-wide cost with `maxCumulatedCost`](https://developers.kardinal.ai/guides/cost-modeling#capping-a-fleet-wide-cost-with-maxcumulatedcost) and [Handling infeasibility](https://developers.kardinal.ai/guides/handling-infeasibility.md).
* A vehicle that does several trips can serve the set across its trips. See [Multi-trip tours](https://developers.kardinal.ai/guides/multi-trip-tours.md).

In the sandbox, a plan with twenty sets of three stops, each set with its own entry, planned every stop. No set was split. The same plan without the entries split all twenty sets across the two vans.

For this pattern in a full plan, see the "Trusted-keychain deliveries" sections of [Retail last-mile](https://developers.kardinal.ai/case-studies/retail-last-mile.md#trusted-keychain-deliveries) and [Cold-chain last-mile](https://developers.kardinal.ai/case-studies/cold-chain-last-mile.md#trusted-keychain-deliveries).

## Preferred order between orders

Between two different orders, the engine has no hard precedence. The stops of two orders can come in any order, and on different vehicles. If you want a preference, tag the stops and add the `maximizePrecedences` objective with pairs of tags.

```json theme={null}
"objectives": [
  "maximizeMandatoryStops",
  { "type": "maximizePrecedences", "precedences": [{ "previous": "first", "next": "then" }] },
  "minimizeDistance"
]
```

In the sandbox, a stop tagged `first` was 8 km from the depot, and a stop tagged `then` was 2 km away. Without the objective, the engine planned the near stop first. With the objective, it planned the stop tagged `first` first. This is a preference, not a rule: the engine can break a pair. See [`maximizePrecedences`](https://developers.kardinal.ai/concepts/objectives#maximizeprecedences) for the objective and its limits.

For a rule that the engine always keeps, put the stops in one order. To fix a sequence yourself, see [fixed assignments](https://developers.kardinal.ai/guides/manual-assignment-and-supervision.md).

## Checking the solution

The engine plans the stops that fit, and it lists the others. A stop that the engine can't serve is not an error. These fields of the solution help:

* `tours` holds the waypoints. A stop that the engine planned appears there with its own id.
* `unaffectedStopIds` lists the stops that the engine left out.
* `unaffectedAlternativeIds` lists the candidates of an `AlternativesStop` that were not chosen.
* The `violations` of a tour name the hard rules that the tour breaks. A stop that the engine dropped doesn't appear there.

So a stop id that you sent can be in a tour, in `unaffectedStopIds`, or in `unaffectedAlternativeIds`. The id of an alternatives group is in none of them. Check each of the three lists. A candidate in `unaffectedAlternativeIds` is not a lost stop.

### Example plan

This plan has one van and three orders. The first two orders are a pickup then a delivery, with `successiveStops: true`. The third order is a collection, then an `AlternativesStop` with two drop sites. All the candidates are deliveries. Every stop is open all day, so the order of the stops doesn't depend on road times.

```json theme={null}
{
  "tz": "Europe/Paris",
  "maxOptimizationDuration": "PT5S",
  "resources": [
    {
      "id": "van-1",
      "vehicleProfile": { "type": "car" },
      "workingTimeWindow": { "begin": "2026-11-03 08:00", "end": "2026-11-03 18:00" },
      "departure": { "lon": 2.35, "lat": 48.85 },
      "arrival": { "lon": 2.35, "lat": 48.85 }
    }
  ],
  "orders": [
    {
      "id": "order-1",
      "successiveStops": true,
      "stops": [
        { "type": "single", "id": "pickup-1", "kind": "pickup", "position": { "lon": 2.364, "lat": 48.85 }, "operationDuration": "PT5M" },
        { "type": "single", "id": "delivery-1", "kind": "delivery", "position": { "lon": 2.487, "lat": 48.85 }, "operationDuration": "PT5M" }
      ]
    },
    {
      "id": "order-2",
      "successiveStops": true,
      "stops": [
        { "type": "single", "id": "pickup-2", "kind": "pickup", "position": { "lon": 2.378, "lat": 48.85 }, "operationDuration": "PT5M" },
        { "type": "single", "id": "delivery-2", "kind": "delivery", "position": { "lon": 2.501, "lat": 48.85 }, "operationDuration": "PT5M" }
      ]
    },
    {
      "id": "order-3",
      "stops": [
        { "type": "single", "id": "collect-3", "kind": "pickup", "position": { "lon": 2.309, "lat": 48.85 }, "operationDuration": "PT5M" },
        {
          "type": "alternatives",
          "id": "drop-site",
          "alternatives": [
            { "type": "single", "id": "drop-far", "kind": "delivery", "position": { "lon": 2.145, "lat": 48.85 }, "operationDuration": "PT5M" },
            { "type": "single", "id": "drop-near", "kind": "delivery", "position": { "lon": 2.282, "lat": 48.85 }, "operationDuration": "PT5M" }
          ]
        }
      ]
    }
  ]
}
```

The same plan with the SDKs:

```python Python theme={null}
from kardinal.routing import models
from kardinal.routing.plans import Order, PlanCreate

depot = models.Position(lon=2.35, lat=48.85)


def stop(stop_id, kind, lon):
    return models.SingleStop(
        type="single",
        id=stop_id,
        kind=kind,
        position=models.Position(lon=lon, lat=48.85),
        operationDuration="PT5M",
    )


van = models.Resource(
    id="van-1",
    vehicleProfile=models.VehicleProfileCar(type="car"),
    workingTimeWindow=models.TimeWindow(begin="2026-11-03 08:00", end="2026-11-03 18:00"),
    departure=depot,
    arrival=depot,
)

drop_site = models.AlternativesStop(
    type="alternatives",
    id="drop-site",
    alternatives=[stop("drop-far", "delivery", 2.145), stop("drop-near", "delivery", 2.282)],
)

plan = PlanCreate(
    tz="Europe/Paris",
    maxOptimizationDuration="PT5S",
    resources=[van],
    orders=[
        Order(
            id="order-1",
            successiveStops=True,
            stops=[stop("pickup-1", "pickup", 2.364), stop("delivery-1", "delivery", 2.487)],
        ),
        Order(
            id="order-2",
            successiveStops=True,
            stops=[stop("pickup-2", "pickup", 2.378), stop("delivery-2", "delivery", 2.501)],
        ),
        Order(id="order-3", stops=[stop("collect-3", "pickup", 2.309), drop_site]),
    ],
)
```

```ts TypeScript theme={null}
import type { PlanCreateInput } from "@kardinal-ai/sdk";

const depot = { lon: 2.35, lat: 48.85 };

function stop(id: string, kind: "pickup" | "delivery", lon: number) {
  return {
    type: "single" as const,
    id,
    kind,
    position: { lon, lat: 48.85 },
    operationDuration: "PT5M",
  };
}

const plan: PlanCreateInput = {
  tz: "Europe/Paris",
  maxOptimizationDuration: "PT5S",
  resources: [
    {
      id: "van-1",
      priority: 0,
      vehicleProfile: { type: "car" },
      workingTimeWindow: { begin: "2026-11-03 08:00", end: "2026-11-03 18:00" },
      departure: depot,
      arrival: depot,
    },
  ],
  orders: [
    {
      id: "order-1",
      priority: 0,
      successiveStops: true,
      stops: [stop("pickup-1", "pickup", 2.364), stop("delivery-1", "delivery", 2.487)],
    },
    {
      id: "order-2",
      priority: 0,
      successiveStops: true,
      stops: [stop("pickup-2", "pickup", 2.378), stop("delivery-2", "delivery", 2.501)],
    },
    {
      id: "order-3",
      priority: 0,
      stops: [
        stop("collect-3", "pickup", 2.309),
        {
          type: "alternatives",
          id: "drop-site",
          alternatives: [stop("drop-far", "delivery", 2.145), stop("drop-near", "delivery", 2.282)],
        },
      ],
    },
  ],
};
```

### What the solution shows

The sandbox returns this for the plan, with the offset of `Europe/Paris` and with seconds. Only the fields that matter here are kept. The times come from the sandbox and depend on its routing. The order of the stops is the same in production.

```json theme={null}
{
  "unaffectedStopIds": [],
  "unaffectedAlternativeIds": ["drop-far"],
  "tours": [
    {
      "resourceId": "van-1",
      "isValid": true,
      "wayPoints": [
        { "type": "begin", "departureTime": "2026-11-03T08:00:00+01:00" },
        { "type": "stop", "stopId": "collect-3", "arrivalTime": "2026-11-03T08:03:36+01:00", "departureTime": "2026-11-03T08:08:36+01:00" },
        { "type": "stop", "stopId": "drop-near", "arrivalTime": "2026-11-03T08:10:58+01:00", "departureTime": "2026-11-03T08:15:58+01:00" },
        { "type": "stop", "stopId": "pickup-1", "arrivalTime": "2026-11-03T08:23:10+01:00", "departureTime": "2026-11-03T08:28:10+01:00" },
        { "type": "stop", "stopId": "delivery-1", "arrivalTime": "2026-11-03T08:38:58+01:00", "departureTime": "2026-11-03T08:43:58+01:00" },
        { "type": "stop", "stopId": "pickup-2", "arrivalTime": "2026-11-03T08:53:32+01:00", "departureTime": "2026-11-03T08:58:32+01:00" },
        { "type": "stop", "stopId": "delivery-2", "arrivalTime": "2026-11-03T09:09:20+01:00", "departureTime": "2026-11-03T09:14:20+01:00" },
        { "type": "end", "arrivalTime": "2026-11-03T09:27:36+01:00" }
      ]
    }
  ]
}
```

* Each order with `successiveStops` is visited as a pair: `pickup-1` and `delivery-1`, then `pickup-2` and `delivery-2`.
* The same plan without `successiveStops` gave another sequence in the sandbox: `pickup-1`, `pickup-2`, `delivery-2`, `delivery-1`. So the flag changed the plan.
* The tour names `drop-near`, the chosen candidate. The group id `drop-site` appears nowhere.
* `drop-far` is in `unaffectedAlternativeIds`. It is not a lost stop. `unaffectedStopIds` is empty, because the engine planned every order.

### A check in code

This check compares the solution with the plan that you sent. It maps each candidate id to its group, so it can tell a lost order from a candidate that was not chosen. It reports a single stop that is left out, and a group that has no candidate in a tour. It doesn't report a candidate that is in `unaffectedAlternativeIds`.

```python Python theme={null}
def check_orders(plan, solution):
    """Return one message for each order that the engine did not fully serve."""
    served = {
        waypoint.root.stopId.root
        for tour in solution.tours
        for waypoint in tour.wayPoints
        if waypoint.root.type == "stop"
    }
    left_out = {stop_id.root for stop_id in solution.unaffectedStopIds or []}

    # Map each candidate id to the id of its group.
    group_of = {
        candidate.id.root: stop.id.root
        for order in plan.orders
        for stop in order.stops
        if stop.type == "alternatives"
        for candidate in stop.alternatives
    }
    served_groups = {group_of[stop_id] for stop_id in served if stop_id in group_of}

    problems = []
    for order in plan.orders:
        for stop in order.stops:
            if stop.type == "single" and stop.id.root in left_out:
                problems.append(f"{order.id.root}: {stop.id.root} is left out")
            if stop.type == "alternatives" and stop.id.root not in served_groups:
                problems.append(f"{order.id.root}: no candidate of {stop.id.root} is in a tour")
    return problems
```

```ts TypeScript theme={null}
import type { PlanCreateInput, WaitedSolution } from "@kardinal-ai/sdk";

export function checkOrders(plan: PlanCreateInput, solution: WaitedSolution): string[] {
  const served = new Set(
    (solution.tours ?? []).flatMap((tour) =>
      (tour.wayPoints ?? []).filter((w) => w.type === "stop").map((w) => w.stopId),
    ),
  );
  const leftOut = new Set(solution.unaffectedStopIds ?? []);

  // Map each candidate id to the id of its group.
  const groupOf = new Map<string, string>();
  for (const order of plan.orders ?? []) {
    for (const stop of order.stops) {
      if (stop.type !== "alternatives") continue;
      for (const candidate of stop.alternatives) groupOf.set(candidate.id, stop.id);
    }
  }
  const servedGroups = new Set([...served].map((id) => groupOf.get(id ?? "")));

  const problems: string[] = [];
  for (const order of plan.orders ?? []) {
    for (const stop of order.stops) {
      if (stop.type === "single" && leftOut.has(stop.id)) {
        problems.push(`${order.id}: ${stop.id} is left out`);
      }
      if (stop.type === "alternatives" && !servedGroups.has(stop.id)) {
        problems.push(`${order.id}: no candidate of ${stop.id} is in a tour`);
      }
    }
  }
  return problems;
}
```

On the example plan, the check returns no problem, although `drop-far` is in `unaffectedAlternativeIds`.

Then look at the `violations` of each tour. The API reference names the violation types for the order rules: `successiveStops`, `order` and `maxStopSpan`.


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