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

# Field service day

> A worked V3 example: eight service jobs, three technicians, one solve, then a manual change scored with evaluate and a new job placed with suggest

A heating and electrical contractor in Ghent plans one working day: eight jobs for three technicians who start and end at the same workshop. Some jobs need a gas-certified or electrically qualified technician, two need a boiler from the workshop, two visits to one customer must be done by the same person, and one maintenance visit is optional. Everyone takes a 30-minute lunch break that starts between 12:00 and 13:00.

This page solves that day with `POST /v3/routing/solve`, reads the response, and then reuses the same data for two follow-ups: scoring a manual change with `/v3/routing/evaluate` and placing a new job with `/v3/routing/suggest`. The solve and evaluate responses are real output for the requests shown. Yours will differ in the travel figures, which depend on live road data.

## The scenario

All times in the request are local time on 4 November 2026, written with the `+01:00` offset.

| Job | Work | Arrive between | Needs |
| - | - | - | - |
| `boiler-install-deinze` | 90 min | 08:00 and 12:00 | `gas` skill, one boiler from the workshop |
| `boiler-commission-deinze` | 30 min | 14:00 and 16:30 | `gas` skill, same technician as the install |
| `gas-leak-lokeren` | 60 min | 08:00 and 10:00 | `gas` skill |
| `boiler-install-eeklo` | 90 min | 09:00 and 15:00 | `gas` skill, one boiler from the workshop |
| `ev-charger-wetteren` | 120 min | 08:00 and 17:00 | `electrical` skill |
| `fuse-box-merelbeke` | 45 min | 13:00 and 17:00 | `electrical` skill |
| `thermostat-gent` | 30 min | 10:00 and 12:00 | Nothing: any technician |
| `annual-check-brugge` | 45 min | 15:00 and 17:00 | `gas` skill. Optional, with a `drop_fee` of 30 |

| Technician | Skills | Van |
| - | - | - |
| `anna` | `gas`, `electrical` | Room for one boiler |
| `bram` | `gas` | Room for one boiler |
| `chloe` | `electrical` | Room for one boiler |

How each requirement maps to the request:

* **Time windows.** `time_windows` on each job. They are hard: a job that cannot be reached inside its window is left unassigned.
* **Skills.** `skills` on a job lists what it requires, `skills` on a vehicle lists what the technician provides. A job goes only to a vehicle that provides every skill it names. See [Skills](/guides/vrp/v3/constraints#skills).
* **Boilers.** `delivery: { "boilers": 1 }` on the two install jobs is stock loaded at the workshop and dropped at the job. It counts against the van's `capacity: { "boilers": 1 }`, so the two installs cannot share a van. See [Capacity](/guides/vrp/v3/constraints#capacity).
* **Same technician.** One `same_resource` relation names the two Deinze visits. See [Relations](/guides/vrp/v3/constraints#relations).
* **Lunch.** A `floating` break of 1,800 seconds on each shift, with one window that bounds when the break may start. See [Driver breaks](/guides/vrp/v3/constraints#driver-breaks).
* **Optional visit.** `mandatory: false` with `drop_fee: 30` on the Bruges check. It is served only when serving it costs less than 30. See [Priority and optional tasks](/guides/vrp/v3/constraints#priority-and-optional-tasks).
* **Workshop.** One `depots[]` entry, referenced by id from each shift's `start` and `end`.
* **Cost.** `objective.costs` prices a technician's time at 40 per hour of route and driving at 0.30 per kilometer, labeled `EUR`.

## Solve the day

```bash theme={null}
curl -X POST https://api.solvice.io/v3/routing/solve \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "problem": {
      "jobs": [
        {
          "id": "boiler-install-deinze",
          "location": { "coordinate": [3.528, 50.983] },
          "service_duration_s": 5400,
          "time_windows": [
            { "from": "2026-11-04T08:00:00+01:00", "to": "2026-11-04T12:00:00+01:00" }
          ],
          "skills": [{ "name": "gas" }],
          "delivery": { "boilers": 1 }
        },
        {
          "id": "boiler-commission-deinze",
          "location": { "coordinate": [3.528, 50.983] },
          "service_duration_s": 1800,
          "time_windows": [
            { "from": "2026-11-04T14:00:00+01:00", "to": "2026-11-04T16:30:00+01:00" }
          ],
          "skills": [{ "name": "gas" }]
        },
        {
          "id": "gas-leak-lokeren",
          "location": { "coordinate": [3.99, 51.104] },
          "service_duration_s": 3600,
          "time_windows": [
            { "from": "2026-11-04T08:00:00+01:00", "to": "2026-11-04T10:00:00+01:00" }
          ],
          "skills": [{ "name": "gas" }]
        },
        {
          "id": "boiler-install-eeklo",
          "location": { "coordinate": [3.556, 51.184] },
          "service_duration_s": 5400,
          "time_windows": [
            { "from": "2026-11-04T09:00:00+01:00", "to": "2026-11-04T15:00:00+01:00" }
          ],
          "skills": [{ "name": "gas" }],
          "delivery": { "boilers": 1 }
        },
        {
          "id": "ev-charger-wetteren",
          "location": { "coordinate": [3.883, 51.003] },
          "service_duration_s": 7200,
          "time_windows": [
            { "from": "2026-11-04T08:00:00+01:00", "to": "2026-11-04T17:00:00+01:00" }
          ],
          "skills": [{ "name": "electrical" }]
        },
        {
          "id": "fuse-box-merelbeke",
          "location": { "coordinate": [3.746, 50.994] },
          "service_duration_s": 2700,
          "time_windows": [
            { "from": "2026-11-04T13:00:00+01:00", "to": "2026-11-04T17:00:00+01:00" }
          ],
          "skills": [{ "name": "electrical" }]
        },
        {
          "id": "thermostat-gent",
          "location": { "coordinate": [3.725, 51.054] },
          "service_duration_s": 1800,
          "time_windows": [
            { "from": "2026-11-04T10:00:00+01:00", "to": "2026-11-04T12:00:00+01:00" }
          ]
        },
        {
          "id": "annual-check-brugge",
          "location": { "coordinate": [3.224, 51.209] },
          "service_duration_s": 2700,
          "time_windows": [
            { "from": "2026-11-04T15:00:00+01:00", "to": "2026-11-04T17:00:00+01:00" }
          ],
          "skills": [{ "name": "gas" }],
          "mandatory": false,
          "drop_fee": 30
        }
      ],
      "vehicles": [
        {
          "id": "anna",
          "skills": [{ "name": "gas" }, { "name": "electrical" }],
          "capacity": { "boilers": 1 },
          "shifts": [
            {
              "id": "wed",
              "from": "2026-11-04T08:00:00+01:00",
              "to": "2026-11-04T17:00:00+01:00",
              "start": { "depot": "workshop" },
              "end": { "depot": "workshop" },
              "breaks": [
                {
                  "type": "floating",
                  "duration_s": 1800,
                  "windows": [
                    { "from": "2026-11-04T12:00:00+01:00", "to": "2026-11-04T13:00:00+01:00" }
                  ]
                }
              ]
            }
          ]
        },
        {
          "id": "bram",
          "skills": [{ "name": "gas" }],
          "capacity": { "boilers": 1 },
          "shifts": [
            {
              "id": "wed",
              "from": "2026-11-04T08:00:00+01:00",
              "to": "2026-11-04T17:00:00+01:00",
              "start": { "depot": "workshop" },
              "end": { "depot": "workshop" },
              "breaks": [
                {
                  "type": "floating",
                  "duration_s": 1800,
                  "windows": [
                    { "from": "2026-11-04T12:00:00+01:00", "to": "2026-11-04T13:00:00+01:00" }
                  ]
                }
              ]
            }
          ]
        },
        {
          "id": "chloe",
          "skills": [{ "name": "electrical" }],
          "capacity": { "boilers": 1 },
          "shifts": [
            {
              "id": "wed",
              "from": "2026-11-04T08:00:00+01:00",
              "to": "2026-11-04T17:00:00+01:00",
              "start": { "depot": "workshop" },
              "end": { "depot": "workshop" },
              "breaks": [
                {
                  "type": "floating",
                  "duration_s": 1800,
                  "windows": [
                    { "from": "2026-11-04T12:00:00+01:00", "to": "2026-11-04T13:00:00+01:00" }
                  ]
                }
              ]
            }
          ]
        }
      ],
      "depots": [{ "id": "workshop", "location": { "coordinate": [3.742, 51.035] } }],
      "relations": [
        {
          "type": "same_resource",
          "job_ids": ["boiler-install-deinze", "boiler-commission-deinze"]
        }
      ],
      "objective": {
        "costs": { "per_route_hour": 40, "per_travel_km": 0.3, "currency": "EUR" }
      }
    },
    "options": { "time_limit_s": 2 }
  }'
```

`options.time_limit_s` gives the search two seconds. Add `?validate_only=true` to the URL to check the body first without solving.

## Read the response

The response below is the real output for that request, with one thing trimmed: each stop's `snapped_location` (the coordinate snapped to the road network) is removed to keep it short. Response times are UTC, so `07:00:00Z` is 08:00 local time.

```json theme={null}
{
  "summary": {
    "status": "partial",
    "vehicles_used": 2,
    "jobs_assigned": 7,
    "jobs_unassigned": 1,
    "total_distance_m": 194699,
    "total_duration_s": 11341,
    "score": {
      "violations": 0,
      "unserved_priority": 0,
      "cost": 583.48241,
      "currency": "EUR",
      "components": {
        "fixed": 0.0,
        "time": 495.07271,
        "distance": 58.4097,
        "waiting": 0.0,
        "overtime": 0.0,
        "lateness": 0.0,
        "window_choice": 0.0,
        "target": 0.0,
        "tag_preference": 0.0,
        "depot_visits": 0.0,
        "grouping": 0.0,
        "drop_fees": 30.0,
        "other": 0.0
      }
    },
    "elapsed_ms": 2352,
    "iterations": 75810
  },
  "routes": [
    {
      "vehicle": "anna",
      "shift": "wed",
      "distance_m": 93047,
      "duration_s": 5690,
      "shift_duration_s": 28110,
      "overtime_s": 0,
      "load_peak": { "boilers": 1 },
      "stops": [
        {
          "type": "start",
          "depot": "workshop",
          "location": [3.742, 51.035],
          "arrival": "2026-11-04T07:00:00Z",
          "departure": "2026-11-04T07:00:00Z",
          "service_s": 0,
          "load_after": { "boilers": 1 }
        },
        {
          "type": "job",
          "id": "ev-charger-wetteren",
          "location": [3.883, 51.003],
          "arrival": "2026-11-04T07:19:28Z",
          "departure": "2026-11-04T09:19:28Z",
          "wait_s": 0,
          "service_s": 7200,
          "travel_time_s": 1168,
          "travel_distance_m": 19168,
          "lateness_s": 0,
          "load_after": { "boilers": 1 }
        },
        {
          "type": "job",
          "id": "thermostat-gent",
          "location": [3.725, 51.054],
          "arrival": "2026-11-04T09:39:50Z",
          "departure": "2026-11-04T10:09:50Z",
          "wait_s": 0,
          "service_s": 1800,
          "travel_time_s": 1222,
          "travel_distance_m": 19852,
          "lateness_s": 0,
          "load_after": { "boilers": 1 }
        },
        {
          "type": "break",
          "arrival": "2026-11-04T11:00:00Z",
          "departure": "2026-11-04T11:30:00Z",
          "service_s": 1800,
          "load_after": { "boilers": 1 }
        },
        {
          "type": "job",
          "id": "boiler-install-deinze",
          "location": [3.528, 50.983],
          "arrival": "2026-11-04T10:31:20Z",
          "departure": "2026-11-04T13:00:00Z",
          "wait_s": 0,
          "service_s": 5400,
          "travel_time_s": 1290,
          "travel_distance_m": 23364,
          "lateness_s": 0,
          "load_after": { "boilers": 0 }
        },
        {
          "type": "job",
          "id": "boiler-commission-deinze",
          "location": [3.528, 50.983],
          "arrival": "2026-11-04T13:00:00Z",
          "departure": "2026-11-04T13:30:00Z",
          "wait_s": 0,
          "service_s": 1800,
          "travel_time_s": 0,
          "travel_distance_m": 0,
          "lateness_s": 0,
          "load_after": { "boilers": 0 }
        },
        {
          "type": "job",
          "id": "fuse-box-merelbeke",
          "location": [3.746, 50.994],
          "arrival": "2026-11-04T13:52:42Z",
          "departure": "2026-11-04T14:37:42Z",
          "wait_s": 0,
          "service_s": 2700,
          "travel_time_s": 1362,
          "travel_distance_m": 24834,
          "lateness_s": 0,
          "load_after": { "boilers": 0 }
        },
        {
          "type": "end",
          "depot": "workshop",
          "location": [3.742, 51.035],
          "arrival": "2026-11-04T14:48:30Z",
          "departure": "2026-11-04T14:48:30Z",
          "service_s": 0,
          "travel_time_s": 648,
          "travel_distance_m": 5829,
          "load_after": { "boilers": 0 }
        }
      ]
    },
    {
      "vehicle": "bram",
      "shift": "wed",
      "distance_m": 101652,
      "duration_s": 5651,
      "shift_duration_s": 16451,
      "overtime_s": 0,
      "load_peak": { "boilers": 1 },
      "stops": [
        {
          "type": "start",
          "depot": "workshop",
          "location": [3.742, 51.035],
          "arrival": "2026-11-04T07:00:00Z",
          "departure": "2026-11-04T07:00:00Z",
          "service_s": 0,
          "load_after": { "boilers": 1 }
        },
        {
          "type": "job",
          "id": "gas-leak-lokeren",
          "location": [3.99, 51.104],
          "arrival": "2026-11-04T07:22:16Z",
          "departure": "2026-11-04T08:22:16Z",
          "wait_s": 0,
          "service_s": 3600,
          "travel_time_s": 1336,
          "travel_distance_m": 26742,
          "lateness_s": 0,
          "load_after": { "boilers": 1 }
        },
        {
          "type": "job",
          "id": "boiler-install-eeklo",
          "location": [3.556, 51.184],
          "arrival": "2026-11-04T09:03:41Z",
          "departure": "2026-11-04T10:33:41Z",
          "wait_s": 0,
          "service_s": 5400,
          "travel_time_s": 2485,
          "travel_distance_m": 43387,
          "lateness_s": 0,
          "load_after": { "boilers": 0 }
        },
        {
          "type": "break",
          "arrival": "2026-11-04T11:04:11Z",
          "departure": "2026-11-04T11:34:11Z",
          "service_s": 1800,
          "load_after": { "boilers": 0 }
        },
        {
          "type": "end",
          "depot": "workshop",
          "location": [3.742, 51.035],
          "arrival": "2026-11-04T11:04:11Z",
          "departure": "2026-11-04T11:34:11Z",
          "service_s": 0,
          "travel_time_s": 1830,
          "travel_distance_m": 31523,
          "load_after": { "boilers": 0 }
        }
      ]
    }
  ],
  "plan": {
    "routes": [
      {
        "vehicle": "anna",
        "shift": "wed",
        "stops": [
          { "type": "job", "id": "ev-charger-wetteren" },
          { "type": "job", "id": "thermostat-gent" },
          { "type": "job", "id": "boiler-install-deinze" },
          { "type": "job", "id": "boiler-commission-deinze" },
          { "type": "job", "id": "fuse-box-merelbeke" }
        ]
      },
      {
        "vehicle": "bram",
        "shift": "wed",
        "stops": [
          { "type": "job", "id": "gas-leak-lokeren" },
          { "type": "job", "id": "boiler-install-eeklo" }
        ]
      }
    ]
  },
  "unassigned": [
    {
      "job_id": "annual-check-brugge",
      "category": "priced_out",
      "reasons": [
        {
          "code": "SOLVER_LIMIT",
          "message": "A feasible insertion exists but the solver did not assign it within the time budget"
        }
      ],
      "relaxations": []
    }
  ]
}
```

### Summary

* **`status`** is `partial`: seven of the eight jobs are on a route and one is in `unassigned[]`. The call still returns HTTP `200`.
* **`vehicles_used`** is `2`. `routes[]` has an entry for `anna` and one for `bram`. `chloe` serves nothing, and a vehicle shift with no stops gets no `routes[]` entry. This request puts no price on using a vehicle; see [Objective function](/guides/vrp/v3/concepts/scoring-explanation) for how to add one.
* **`total_distance_m`** and **`total_duration_s`** are sums over the routes. `total_duration_s` is driving time only: 5,690 s + 5,651 s = 11,341 s.

### Routes and stops

Each route names its `vehicle` and `shift` by the ids you sent, and lists its stops in visiting order between a `start` and an `end` stop. Those two stops carry `depot: "workshop"` because the shift references the depot by id.

Anna's day, in local time with the seconds dropped:

| Stop | Arrives | Leaves | What the response shows |
| - | - | - | - |
| `start` | 08:00 | 08:00 | `load_after` is `{ "boilers": 1 }`: the boiler for Deinze is on board from the workshop |
| `ev-charger-wetteren` | 08:19 | 10:19 | An `electrical` job; `service_s` is the 7,200 s you sent |
| `thermostat-gent` | 10:39 | 11:09 | Inside its 10:00 to 12:00 window, so `wait_s` is `0` |
| `break` | 12:00 | 12:30 | The lunch break, starting when its window opens |
| `boiler-install-deinze` | 11:31 | 14:00 | `load_after` drops to `{ "boilers": 0 }` |
| `boiler-commission-deinze` | 14:00 | 14:30 | Same location, so `travel_time_s` and `travel_distance_m` are `0` |
| `fuse-box-merelbeke` | 14:52 | 15:37 | The second `electrical` job |
| `end` | 15:48 | 15:48 | Back at the workshop |

What to take from it:

* **Skills.** Every `gas` job is on `anna` or `bram`, and both `electrical` jobs are on `anna`, who has both skills.
* **Capacity.** Each van carries one boiler, so the two installs are on different routes. `load_peak` is the highest load on the route: `{ "boilers": 1 }` on both.
* **The relation.** `boiler-install-deinze` and `boiler-commission-deinze` are both on `anna`, as the `same_resource` relation requires.
* **Time windows.** `arrival` is when the vehicle reaches the stop. The commissioning visit starts at 14:00, the moment its window opens.
* **Breaks.** A `break` stop has no `id` and no `location`. Its `arrival` and `departure` are the start and end of the break. It is listed directly before the next stop on the route, and in this response each break starts after the vehicle has reached that stop, so a break's `arrival` can be later than the `arrival` of the stop listed after it. Anna reaches Deinze at 11:31, takes her break from 12:00 to 12:30, and then does the 90-minute install. That is why the install's `departure` minus its `arrival` is longer than its `service_s`: the interval also holds the break and the time before it. Bram has no job left when his break window opens at 12:00. A break whose window opens before the shift's `to` is still taken on a route that is in use, so his lands at the end of the route: he reaches the workshop at 12:04 and the `end` stop has `arrival` 12:04 and `departure` 12:34.
* **Durations.** `duration_s` is driving time only. `shift_duration_s` is the route's span from start to finish, including service, waiting and the break: 28,110 s for `anna` (08:00 to 15:48) and 16,451 s for `bram` (08:00 to 12:34).
* **`plan`.** The same routes in the shape a request accepts: task stops only, without start, end and break stops. The two follow-ups below send it back.

### The unassigned job

```json theme={null}
{
  "unassigned": [
    {
      "job_id": "annual-check-brugge",
      "category": "priced_out",
      "reasons": [
        {
          "code": "SOLVER_LIMIT",
          "message": "A feasible insertion exists but the solver did not assign it within the time budget"
        }
      ],
      "relaxations": []
    }
  ]
}
```

Read `category` first. `priced_out` means the job fits, it has a `drop_fee`, and serving it would cost at least that fee, so the solver paid the fee instead. The 30 shows up in `score.components.drop_fees`, and `score.unserved_priority` stays `0` because the job was not must-serve.

The reason code for a `priced_out` job is `SOLVER_LIMIT`, and its `message` mentions the time budget. That code is shared with the `search_limit` category, so branch on `category` and not on the code: a longer `time_limit_s` would leave this job out again. `relaxations` is empty for this job. [Errors](/guides/vrp/v3/errors#categories) lists every category and reason code.

### Score

`score.cost` is the sum of `score.components`, in the currency you labeled:

| Component | Value | Where it comes from |
| - | - | - |
| `time` | 495.07271 | Both routes' `shift_duration_s`, 28,110 s + 16,451 s = 44,561 s, at 40 per hour. The hourly rate is applied per second at five decimals: 44,561 × 0.01111 |
| `distance` | 58.4097 | 194.699 km × 0.30 |
| `drop_fees` | 30.0 | The fee for `annual-check-brugge` |
| `cost` | 583.48241 | The sum |

Every component is always present, so the ones this request does not use are `0.0`. `violations` is always `0` on a solve response. See [Objective function](/guides/vrp/v3/concepts/scoring-explanation) for each component.

## Score a manual change with evaluate

A dispatcher decides the Bruges check has to happen today and gives it to `bram`, who is free after lunch. Before committing that, send the edited plan to `/v3/routing/evaluate`. The `problem` is unchanged. The `plan` is the solve response's `plan` with one stop appended to Bram's route:

```json theme={null}
{
  "plan": {
    "routes": [
      {
        "vehicle": "anna",
        "shift": "wed",
        "stops": [
          { "type": "job", "id": "ev-charger-wetteren" },
          { "type": "job", "id": "thermostat-gent" },
          { "type": "job", "id": "boiler-install-deinze" },
          { "type": "job", "id": "boiler-commission-deinze" },
          { "type": "job", "id": "fuse-box-merelbeke" }
        ]
      },
      {
        "vehicle": "bram",
        "shift": "wed",
        "stops": [
          { "type": "job", "id": "gas-leak-lokeren" },
          { "type": "job", "id": "boiler-install-eeklo" },
          { "type": "job", "id": "annual-check-brugge" }
        ]
      }
    ]
  }
}
```

Evaluate takes `problem` and `plan` and no `options`.

<Accordion title="Complete evaluate request">
  ```bash theme={null}
  curl -X POST https://api.solvice.io/v3/routing/evaluate \
    -H "Authorization: YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "problem": {
        "jobs": [
          {
            "id": "boiler-install-deinze",
            "location": { "coordinate": [3.528, 50.983] },
            "service_duration_s": 5400,
            "time_windows": [
              { "from": "2026-11-04T08:00:00+01:00", "to": "2026-11-04T12:00:00+01:00" }
            ],
            "skills": [{ "name": "gas" }],
            "delivery": { "boilers": 1 }
          },
          {
            "id": "boiler-commission-deinze",
            "location": { "coordinate": [3.528, 50.983] },
            "service_duration_s": 1800,
            "time_windows": [
              { "from": "2026-11-04T14:00:00+01:00", "to": "2026-11-04T16:30:00+01:00" }
            ],
            "skills": [{ "name": "gas" }]
          },
          {
            "id": "gas-leak-lokeren",
            "location": { "coordinate": [3.99, 51.104] },
            "service_duration_s": 3600,
            "time_windows": [
              { "from": "2026-11-04T08:00:00+01:00", "to": "2026-11-04T10:00:00+01:00" }
            ],
            "skills": [{ "name": "gas" }]
          },
          {
            "id": "boiler-install-eeklo",
            "location": { "coordinate": [3.556, 51.184] },
            "service_duration_s": 5400,
            "time_windows": [
              { "from": "2026-11-04T09:00:00+01:00", "to": "2026-11-04T15:00:00+01:00" }
            ],
            "skills": [{ "name": "gas" }],
            "delivery": { "boilers": 1 }
          },
          {
            "id": "ev-charger-wetteren",
            "location": { "coordinate": [3.883, 51.003] },
            "service_duration_s": 7200,
            "time_windows": [
              { "from": "2026-11-04T08:00:00+01:00", "to": "2026-11-04T17:00:00+01:00" }
            ],
            "skills": [{ "name": "electrical" }]
          },
          {
            "id": "fuse-box-merelbeke",
            "location": { "coordinate": [3.746, 50.994] },
            "service_duration_s": 2700,
            "time_windows": [
              { "from": "2026-11-04T13:00:00+01:00", "to": "2026-11-04T17:00:00+01:00" }
            ],
            "skills": [{ "name": "electrical" }]
          },
          {
            "id": "thermostat-gent",
            "location": { "coordinate": [3.725, 51.054] },
            "service_duration_s": 1800,
            "time_windows": [
              { "from": "2026-11-04T10:00:00+01:00", "to": "2026-11-04T12:00:00+01:00" }
            ]
          },
          {
            "id": "annual-check-brugge",
            "location": { "coordinate": [3.224, 51.209] },
            "service_duration_s": 2700,
            "time_windows": [
              { "from": "2026-11-04T15:00:00+01:00", "to": "2026-11-04T17:00:00+01:00" }
            ],
            "skills": [{ "name": "gas" }],
            "mandatory": false,
            "drop_fee": 30
          }
        ],
        "vehicles": [
          {
            "id": "anna",
            "skills": [{ "name": "gas" }, { "name": "electrical" }],
            "capacity": { "boilers": 1 },
            "shifts": [
              {
                "id": "wed",
                "from": "2026-11-04T08:00:00+01:00",
                "to": "2026-11-04T17:00:00+01:00",
                "start": { "depot": "workshop" },
                "end": { "depot": "workshop" },
                "breaks": [
                  {
                    "type": "floating",
                    "duration_s": 1800,
                    "windows": [
                      { "from": "2026-11-04T12:00:00+01:00", "to": "2026-11-04T13:00:00+01:00" }
                    ]
                  }
                ]
              }
            ]
          },
          {
            "id": "bram",
            "skills": [{ "name": "gas" }],
            "capacity": { "boilers": 1 },
            "shifts": [
              {
                "id": "wed",
                "from": "2026-11-04T08:00:00+01:00",
                "to": "2026-11-04T17:00:00+01:00",
                "start": { "depot": "workshop" },
                "end": { "depot": "workshop" },
                "breaks": [
                  {
                    "type": "floating",
                    "duration_s": 1800,
                    "windows": [
                      { "from": "2026-11-04T12:00:00+01:00", "to": "2026-11-04T13:00:00+01:00" }
                    ]
                  }
                ]
              }
            ]
          },
          {
            "id": "chloe",
            "skills": [{ "name": "electrical" }],
            "capacity": { "boilers": 1 },
            "shifts": [
              {
                "id": "wed",
                "from": "2026-11-04T08:00:00+01:00",
                "to": "2026-11-04T17:00:00+01:00",
                "start": { "depot": "workshop" },
                "end": { "depot": "workshop" },
                "breaks": [
                  {
                    "type": "floating",
                    "duration_s": 1800,
                    "windows": [
                      { "from": "2026-11-04T12:00:00+01:00", "to": "2026-11-04T13:00:00+01:00" }
                    ]
                  }
                ]
              }
            ]
          }
        ],
        "depots": [{ "id": "workshop", "location": { "coordinate": [3.742, 51.035] } }],
        "relations": [
          {
            "type": "same_resource",
            "job_ids": ["boiler-install-deinze", "boiler-commission-deinze"]
          }
        ],
        "objective": {
          "costs": { "per_route_hour": 40, "per_travel_km": 0.3, "currency": "EUR" }
        }
      },
      "plan": {
        "routes": [
          {
            "vehicle": "anna",
            "shift": "wed",
            "stops": [
              { "type": "job", "id": "ev-charger-wetteren" },
              { "type": "job", "id": "thermostat-gent" },
              { "type": "job", "id": "boiler-install-deinze" },
              { "type": "job", "id": "boiler-commission-deinze" },
              { "type": "job", "id": "fuse-box-merelbeke" }
            ]
          },
          {
            "vehicle": "bram",
            "shift": "wed",
            "stops": [
              { "type": "job", "id": "gas-leak-lokeren" },
              { "type": "job", "id": "boiler-install-eeklo" },
              { "type": "job", "id": "annual-check-brugge" }
            ]
          }
        ]
      }
    }'
  ```
</Accordion>

The real response, trimmed to `summary`, Bram's route and `unassigned`. Anna's route comes back exactly as in the solve response, and `plan` echoes the plan you sent. `snapped_location` is removed as before.

```json theme={null}
{
  "summary": {
    "status": "feasible",
    "vehicles_used": 2,
    "jobs_assigned": 8,
    "jobs_unassigned": 0,
    "total_distance_m": 248275,
    "total_duration_s": 13857,
    "score": {
      "violations": 0,
      "unserved_priority": 0,
      "cost": 721.54001,
      "currency": "EUR",
      "components": {
        "fixed": 0.0,
        "time": 647.05751,
        "distance": 74.4825,
        "waiting": 0.0,
        "overtime": 0.0,
        "lateness": 0.0,
        "window_choice": 0.0,
        "target": 0.0,
        "tag_preference": 0.0,
        "depot_visits": 0.0,
        "grouping": 0.0,
        "drop_fees": 0.0,
        "other": 0.0
      }
    },
    "elapsed_ms": 107
  },
  "routes": [
    {
      "vehicle": "bram",
      "shift": "wed",
      "distance_m": 155228,
      "duration_s": 8167,
      "shift_duration_s": 30131,
      "overtime_s": 0,
      "load_peak": { "boilers": 1 },
      "stops": [
        {
          "type": "start",
          "depot": "workshop",
          "location": [3.742, 51.035],
          "arrival": "2026-11-04T07:00:00Z",
          "departure": "2026-11-04T07:00:00Z",
          "service_s": 0,
          "load_after": { "boilers": 1 }
        },
        {
          "type": "job",
          "id": "gas-leak-lokeren",
          "location": [3.99, 51.104],
          "arrival": "2026-11-04T07:22:16Z",
          "departure": "2026-11-04T08:22:16Z",
          "wait_s": 0,
          "service_s": 3600,
          "travel_time_s": 1336,
          "travel_distance_m": 26742,
          "lateness_s": 0,
          "load_after": { "boilers": 1 }
        },
        {
          "type": "job",
          "id": "boiler-install-eeklo",
          "location": [3.556, 51.184],
          "arrival": "2026-11-04T09:03:41Z",
          "departure": "2026-11-04T10:33:41Z",
          "wait_s": 0,
          "service_s": 5400,
          "travel_time_s": 2485,
          "travel_distance_m": 43387,
          "lateness_s": 0,
          "load_after": { "boilers": 0 }
        },
        {
          "type": "break",
          "arrival": "2026-11-04T11:08:56Z",
          "departure": "2026-11-04T11:38:56Z",
          "service_s": 1800,
          "load_after": { "boilers": 0 }
        },
        {
          "type": "job",
          "id": "annual-check-brugge",
          "location": [3.224, 51.209],
          "arrival": "2026-11-04T11:08:56Z",
          "departure": "2026-11-04T14:45:00Z",
          "wait_s": 8464,
          "service_s": 2700,
          "travel_time_s": 2115,
          "travel_distance_m": 30761,
          "lateness_s": 0,
          "load_after": { "boilers": 0 }
        },
        {
          "type": "end",
          "depot": "workshop",
          "location": [3.742, 51.035],
          "arrival": "2026-11-04T15:22:11Z",
          "departure": "2026-11-04T15:22:11Z",
          "service_s": 0,
          "travel_time_s": 2231,
          "travel_distance_m": 54338,
          "load_after": { "boilers": 0 }
        }
      ]
    }
  ],
  "unassigned": []
}
```

How to read it:

* **`status`** is `feasible`: the edited plan breaks no hard rule. On evaluate the value is `feasible` or `infeasible`, and a broken rule is listed as a `violations[]` entry on the stop or route it concerns. There is no `iterations` field, because nothing is searched.
* **The new stop.** Bram reaches Bruges at 12:08 local time and takes his lunch break there. The job's window opens at 15:00, so `wait_s` is 8,464 s, and he leaves at 15:45. His route now ends at 16:22 instead of 12:34.
* **`unassigned`** is empty: every job of `problem` is in the plan.

The change has a price. `cost` rises from 583.48241 to 721.54001, which is 138.0576 more:

| Component | Solve | Edited plan | Difference |
| - | - | - | - |
| `time` | 495.07271 | 647.05751 | +151.9848: Bram's route is 13,680 s longer |
| `distance` | 58.4097 | 74.4825 | +16.0728: 53.576 km more |
| `drop_fees` | 30.0 | 0.0 | -30.0 |

Serving the visit this way costs 168.0576 against a fee of 30, which is why the solve left it out.

If the visit must always be planned, remove `mandatory: false` and `drop_fee` from the job, or raise the fee to what a missed visit costs you. See [Evaluate](/guides/vrp/v3/evaluate) for the violation types.

## Place a new job with suggest

At 11:00 a customer in Destelbergen reports a leaking radiator and can be visited between 13:00 and 17:00. The routes are already under way, so ask where the new job fits without rearranging them. A suggest request has three parts:

* `problem`: the same problem with the new job added to `jobs`.
* `plan`: the plan you are keeping. Here it is the solve response's `plan`, unchanged.
* `options.tasks`: the ids to place.

The job to add:

```json theme={null}
{
  "id": "radiator-leak-destelbergen",
  "location": { "coordinate": [3.8, 51.058] },
  "service_duration_s": 2700,
  "time_windows": [{ "from": "2026-11-04T13:00:00+01:00", "to": "2026-11-04T17:00:00+01:00" }],
  "skills": [{ "name": "gas" }]
}
```

And the options:

```json theme={null}
{ "options": { "tasks": ["radiator-leak-destelbergen"], "max_suggestions": 3 } }
```

<Accordion title="Complete suggest request">
  ```bash theme={null}
  curl -X POST https://api.solvice.io/v3/routing/suggest \
    -H "Authorization: YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "problem": {
        "jobs": [
          {
            "id": "boiler-install-deinze",
            "location": { "coordinate": [3.528, 50.983] },
            "service_duration_s": 5400,
            "time_windows": [
              { "from": "2026-11-04T08:00:00+01:00", "to": "2026-11-04T12:00:00+01:00" }
            ],
            "skills": [{ "name": "gas" }],
            "delivery": { "boilers": 1 }
          },
          {
            "id": "boiler-commission-deinze",
            "location": { "coordinate": [3.528, 50.983] },
            "service_duration_s": 1800,
            "time_windows": [
              { "from": "2026-11-04T14:00:00+01:00", "to": "2026-11-04T16:30:00+01:00" }
            ],
            "skills": [{ "name": "gas" }]
          },
          {
            "id": "gas-leak-lokeren",
            "location": { "coordinate": [3.99, 51.104] },
            "service_duration_s": 3600,
            "time_windows": [
              { "from": "2026-11-04T08:00:00+01:00", "to": "2026-11-04T10:00:00+01:00" }
            ],
            "skills": [{ "name": "gas" }]
          },
          {
            "id": "boiler-install-eeklo",
            "location": { "coordinate": [3.556, 51.184] },
            "service_duration_s": 5400,
            "time_windows": [
              { "from": "2026-11-04T09:00:00+01:00", "to": "2026-11-04T15:00:00+01:00" }
            ],
            "skills": [{ "name": "gas" }],
            "delivery": { "boilers": 1 }
          },
          {
            "id": "ev-charger-wetteren",
            "location": { "coordinate": [3.883, 51.003] },
            "service_duration_s": 7200,
            "time_windows": [
              { "from": "2026-11-04T08:00:00+01:00", "to": "2026-11-04T17:00:00+01:00" }
            ],
            "skills": [{ "name": "electrical" }]
          },
          {
            "id": "fuse-box-merelbeke",
            "location": { "coordinate": [3.746, 50.994] },
            "service_duration_s": 2700,
            "time_windows": [
              { "from": "2026-11-04T13:00:00+01:00", "to": "2026-11-04T17:00:00+01:00" }
            ],
            "skills": [{ "name": "electrical" }]
          },
          {
            "id": "thermostat-gent",
            "location": { "coordinate": [3.725, 51.054] },
            "service_duration_s": 1800,
            "time_windows": [
              { "from": "2026-11-04T10:00:00+01:00", "to": "2026-11-04T12:00:00+01:00" }
            ]
          },
          {
            "id": "annual-check-brugge",
            "location": { "coordinate": [3.224, 51.209] },
            "service_duration_s": 2700,
            "time_windows": [
              { "from": "2026-11-04T15:00:00+01:00", "to": "2026-11-04T17:00:00+01:00" }
            ],
            "skills": [{ "name": "gas" }],
            "mandatory": false,
            "drop_fee": 30
          },
          {
            "id": "radiator-leak-destelbergen",
            "location": { "coordinate": [3.8, 51.058] },
            "service_duration_s": 2700,
            "time_windows": [
              { "from": "2026-11-04T13:00:00+01:00", "to": "2026-11-04T17:00:00+01:00" }
            ],
            "skills": [{ "name": "gas" }]
          }
        ],
        "vehicles": [
          {
            "id": "anna",
            "skills": [{ "name": "gas" }, { "name": "electrical" }],
            "capacity": { "boilers": 1 },
            "shifts": [
              {
                "id": "wed",
                "from": "2026-11-04T08:00:00+01:00",
                "to": "2026-11-04T17:00:00+01:00",
                "start": { "depot": "workshop" },
                "end": { "depot": "workshop" },
                "breaks": [
                  {
                    "type": "floating",
                    "duration_s": 1800,
                    "windows": [
                      { "from": "2026-11-04T12:00:00+01:00", "to": "2026-11-04T13:00:00+01:00" }
                    ]
                  }
                ]
              }
            ]
          },
          {
            "id": "bram",
            "skills": [{ "name": "gas" }],
            "capacity": { "boilers": 1 },
            "shifts": [
              {
                "id": "wed",
                "from": "2026-11-04T08:00:00+01:00",
                "to": "2026-11-04T17:00:00+01:00",
                "start": { "depot": "workshop" },
                "end": { "depot": "workshop" },
                "breaks": [
                  {
                    "type": "floating",
                    "duration_s": 1800,
                    "windows": [
                      { "from": "2026-11-04T12:00:00+01:00", "to": "2026-11-04T13:00:00+01:00" }
                    ]
                  }
                ]
              }
            ]
          },
          {
            "id": "chloe",
            "skills": [{ "name": "electrical" }],
            "capacity": { "boilers": 1 },
            "shifts": [
              {
                "id": "wed",
                "from": "2026-11-04T08:00:00+01:00",
                "to": "2026-11-04T17:00:00+01:00",
                "start": { "depot": "workshop" },
                "end": { "depot": "workshop" },
                "breaks": [
                  {
                    "type": "floating",
                    "duration_s": 1800,
                    "windows": [
                      { "from": "2026-11-04T12:00:00+01:00", "to": "2026-11-04T13:00:00+01:00" }
                    ]
                  }
                ]
              }
            ]
          }
        ],
        "depots": [{ "id": "workshop", "location": { "coordinate": [3.742, 51.035] } }],
        "relations": [
          {
            "type": "same_resource",
            "job_ids": ["boiler-install-deinze", "boiler-commission-deinze"]
          }
        ],
        "objective": {
          "costs": { "per_route_hour": 40, "per_travel_km": 0.3, "currency": "EUR" }
        }
      },
      "plan": {
        "routes": [
          {
            "vehicle": "anna",
            "shift": "wed",
            "stops": [
              { "type": "job", "id": "ev-charger-wetteren" },
              { "type": "job", "id": "thermostat-gent" },
              { "type": "job", "id": "boiler-install-deinze" },
              { "type": "job", "id": "boiler-commission-deinze" },
              { "type": "job", "id": "fuse-box-merelbeke" }
            ]
          },
          {
            "vehicle": "bram",
            "shift": "wed",
            "stops": [
              { "type": "job", "id": "gas-leak-lokeren" },
              { "type": "job", "id": "boiler-install-eeklo" }
            ]
          }
        ]
      },
      "options": { "tasks": ["radiator-leak-destelbergen"], "max_suggestions": 3 }
    }'
  ```
</Accordion>

The response has one `suggestions[]` entry for `radiator-leak-destelbergen` with up to three `options`, cheapest first. Each option names the `vehicle`, the `position` among that route's task stops, the neighboring stops (`after_job_id`, `before_job_id`), the `arrival` at the new stop, and what the placement adds: `extra_cost` (an integer in units of 1/100,000 of your currency), `extra_distance_m` and `extra_duration_s`. The job requires `gas`, so no option names `chloe`. `annual-check-brugge` is still in `problem` but in neither `plan` nor `options.tasks`, so it stays unassigned and gets no suggestions.

Suggest changes nothing. To act on an option:

<Steps>
  <Step title="Insert the stop">
    Add `{ "type": "job", "id": "radiator-leak-destelbergen" }` to the `plan.routes[]` entry of the option's `vehicle`, at index `position` of its `stops`.
  </Step>

  <Step title="Score the result">
    Send the edited plan to `/v3/routing/evaluate`, as in the previous section, to get the full timeline and cost.
  </Step>
</Steps>

To mark stops that are already done or under way, set `locked_count` on a route in `plan`: no option lands before the locked stops. This page does not show a suggest response; [Suggest](/guides/vrp/v3/suggest) has an annotated one and the full list of option fields.

## Next steps

<Columns cols={2}>
  <Card title="The V3 API model" icon="cube" href="/guides/vrp/v3/api-design">
    Every request and response field.
  </Card>

  <Card title="Constraints" icon="cog" href="/guides/vrp/v3/constraints">
    The rules used here, and the ones this example leaves out.
  </Card>

  <Card title="Evaluate" icon="scale-balanced" href="/guides/vrp/v3/evaluate">
    The plan shape and every violation type.
  </Card>

  <Card title="Suggest" icon="lightbulb" href="/guides/vrp/v3/suggest">
    Reading and applying insertion options.
  </Card>
</Columns>


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