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

# Handling infeasibility

> Interpret a no-solution response and diagnose the constraint at fault.

Kardinal doesn't reject an infeasible plan outright — see [Hard vs soft constraints](/concepts/hard-vs-soft-constraints#why-a-stop-not-always-the-problem-becomes-infeasible) for why. The API still returns `200` with a solution; infeasibility shows up as specific stops or resources being left out of it. This guide covers how to recognize that and work back to the cause.

## How to recognize an infeasibility response

There is no dedicated "infeasible" status — check these fields on the solution instead:

* **`unaffectedStopIds`** — non-empty means at least one stop could not be planned within the hard constraints. This is the most common signal and the one to check first.
* **`tours[].isValid: false`** — a specific tour violates a hard constraint. This is less common in practice (the engine generally avoids constructing an invalid tour rather than returning one), but check it per-tour rather than assuming the plan is fine because the top-level call succeeded.

If neither of these is populated, the plan is fully feasible as submitted. A non-empty `unusedResourceIds` isn't an infeasibility signal either: a resource left with nothing assigned is the objectives at work (for example `minimizeResources`), not a hard constraint failing — see [Objectives and how they're ranked](/concepts/objectives) for how the objective order decides this.

## Diagnostic method: which constraint is at fault

For each stop in `unaffectedStopIds`, walk through the hard constraints in [Hard vs soft constraints](/concepts/hard-vs-soft-constraints) and check them against that stop and the resources that could plausibly serve it:

1. **Time windows.** Is there any resource whose `workingTimeWindow` overlaps at least one of the stop's `authorizedTimeWindows`, once travel time to/from the stop is accounted for? A window that's technically non-empty but unreachable given travel time is the single most common cause — especially if the window came from a `preferredTimeWindows` field that was accidentally modeled as `authorizedTimeWindows` (see [Hard vs soft constraints](/concepts/hard-vs-soft-constraints#a-contractual-window-is-not-automatically-a-hard-one)); a window that's a hard constraint by mistake turns a should-be-late delivery into a dropped one.
2. **Skills.** Does any available resource's `skills` array cover every one of the stop's order's `requiredSkills`? A single missing skill on every resource is enough to make the stop unserviceable.
3. **Capacities.** For each capacity key the stop consumes, does a resource that declares that key have enough remaining headroom at that point in a plausible tour? A key a resource doesn't declare at all is unconstrained on that resource: it never blocks the stop. Remember capacities are free-form and matched by exact string — a typo in a key name (`weight` on the resource, `Weight` on the stop) raises no error: it silently leaves that dimension unlimited for the stop instead of enforcing the resource's ceiling.
4. **Order structure.** If the order has `successiveStops` or a `maxStopSpan`, check whether the timing implied by other constraints (time windows, breaks) makes that structural requirement impossible to satisfy alongside them.
5. **Resource-level bounds.** Would serving this stop push a resource over its `maxWorkingDuration`, `maxDistanceInKm`, or `maxInterStopDistanceInKm` / `maxInterStopDuration`?

Work through these in order — time windows and skills are the fastest to rule in or out and cause the large majority of real-world infeasibility.

### Testing a stop on a specific resource with `state.assignments`

When the preceding checks don't point to an obvious cause, ask the engine directly: rebuild the solution as a locked plan, add the unplanned stop to the tour you expected to serve it, and read the violations the engine reports.

1. **Reproduce the current solution.** From the solution that left the stop unplanned, take each tour's sequence of stops (`tours[].wayPoints`) and turn it into that resource's `state.assignments`, with `mode: "fixed"` on the resource and `status: "assigned"` on every entry. Each stop stays on the same resource and no other stop can be added; the engine only re-sequences each tour's stops according to the plan's objectives, so the plan reproduces the solution's tours.
2. **Insert the unplanned stop.** Add the stop from `unaffectedStopIds` to the tour of the resource you expected to serve it, also with `status: "assigned"`. The engine sequences it with the tour's other stops.
3. **Submit it and read the violations.** In `fixed` mode the engine can neither drop the stop nor move it to another resource. So instead of leaving it in `unaffectedStopIds`, it returns that tour with `isValid: false` and one entry per broken hard constraint in `tours[].violations`. Each entry's `type` names the constraint at fault — most often `authorizedTimeWindow`, `skills`, or `capacity`, but also, for example, `workingTimeWindow`, `maxWorkingDuration`, or `successiveStops`. Constraints that apply to the plan as a whole rather than to one tour, such as a `maxCumulatedCost` entry in `globalConstraints`, are reported in the solution's `globalViolations` instead.

```json theme={null}
{
  "resources": [
    {
      "id": "driver-1",
      "state": {
        "mode": "fixed",
        "assignments": [
          { "type": "stop", "status": "assigned", "stopId": "stop-3" },
          { "type": "stop", "status": "assigned", "stopId": "stop-7" },
          { "type": "stop", "status": "assigned", "stopId": "stop-unplanned" },
          { "type": "stop", "status": "assigned", "stopId": "stop-12" }
        ]
      }
    }
  ]
}
```

This excerpt shows one resource; every other resource carries its own tour the same way, and the request carries the complete plan. Submit it as a separate diagnostic plan (`POST /plans`) rather than as an update of the live one: in `fixed` mode, the engine adds no stop and re-plans nothing. Use the same environment as the solution you're diagnosing — the sandbox computes routes crow-fly, so its timings differ from production.

## Resolution strategies

Once you've identified the binding constraint, the fix is usually one of:

* **Relax the constraint**, if it was set stricter than the business actually requires — the most common example is switching a contractual delivery window from `authorizedTimeWindows` to `preferredTimeWindows` once you confirm a late visit is acceptable.
* **Add or reassign a resource** — a stop with no resource combination that satisfies its skills/capacity/time-window requirements simply needs one that does; this is a fleet-sizing problem more than a modeling one.
* **Make the stop less important — a higher `priority` number, since lower numbers matter more — or mark it `"optional": true`** if it's acceptable for it to be dropped under pressure from higher-priority stops — this doesn't fix infeasibility, but it makes the trade-off explicit and intentional instead of an unplanned side effect.
* **Check `additionalConstraints`/`globalConstraints`** (`incompatibleStopTags`, `forbiddenAssignment`, [`maxStopTagGroups`](/guides/multi-trip-tours#limiting-how-many-times-a-resource-returns-to-the-depot), `maxCumulatedCost`, etc.) for anything scoped to the stop's or resource's tags — these are easy to forget once a plan has grown past its first few constraints.

## See also

* [Hard vs soft constraints](/concepts/hard-vs-soft-constraints) — the conceptual explanation of which fields are walls and which are targets, referenced throughout the preceding diagnostic method.
* [Data model](/reference/data-model) — an overview of everything checked in the diagnostic steps, each part linking to its schema in the API reference.
* [Modeling advanced constraints](/guides/advanced-constraints) — worked examples for capacities, skills, breaks, and multiple time windows.


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