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

# Start here if you're an AI agent

> Ten rules for an AI coding agent that turns a customer's raw data into a V3 Routing API request

> [!NOTE]
>
> **V3 is in beta.** General availability (GA) is planned for January 2027.

You have a customer's raw data and must turn it into a `POST /v3/routing/solve` request a dispatcher can trust. Each rule says what the API does when you get it wrong, and what to hand back.

> [!WARNING]
>
> Written for AI agents. If you are a person, start at the [Quickstart](https://docs.solvice.io/guides/vrp/v3/quickstart.md) instead.

## At a glance

Integration adds the API to an existing app. Modelling turns raw data into a plan; every rule applies to it.

| # | Rule | In short | Matters for |
| - | - | - | - |
| 1 | Validate before you solve | Same pre-travel checks as the real call, free, unbilled | Integration |
| 2 | Never guess a field name | Any unknown field is a `400` `unknown_field` | Both |
| 3 | Treat time windows as hard unless you price them | Lateness is forbidden until `per_late_hour` prices it | Modelling |
| 4 | Choose `drop_cost` or `priority` deliberately | Optional versus ranked must-serve; both is a `400` | Modelling |
| 5 | Never fabricate or round a coordinate | `[longitude, latitude]`, real geocode, or leave the task out | Both |
| 6 | Read `unassigned[].reasons` before you tune | `category` says what fix works; `constraint` names the rule | Both |
| 7 | Use `straight_line` for smoke tests only | No road network; never for a plan a dispatcher drives | Integration |
| 8 | Ask the operation owner on irreducible ambiguity | A default you chose is a business rule you invented | Modelling |
| 9 | Send no placeholder values | The solver plans on every number as if it were true | Both |
| 10 | Deliver a modelling report | Field-status table plus the unassigned list with reasons | Modelling |

## 1. Validate before you solve

Send every request you build to `POST /v3/routing/validate/solve` (or `/validate/evaluate`, `/validate/suggest`) before the real call.

It runs the same request checks and stops there: no travel lookup, no search, no charge. You get `200 {"valid": true, "warnings": []}` or the `400` or `403` the real call would return for those checks. Two checks need travel data and wait for the real call: coordinates spanning more than one region, and whether the `plan` imports. One `plan` check does run here: `/validate/suggest` rejects a `plan` whose stop ids are exactly the problem's task ids, since that leaves nothing to place. The real call checks the rest of the `plan` when it imports it.

Deliver: the validate response, including `warnings[]`.

## 2. Never guess a field name

Take every field name from the [API reference](https://docs.solvice.io/api-reference/vrp/v3/introduction.md). Do not carry names over from V2, another routing API or the customer's columns.

Every V3 object rejects fields it does not define. A typo, a camelCase name, a V2 field or a field on the wrong object is a `400` with item code `unknown_field` and a `pointer` to the key; nothing is ignored.

Deliver: a mapping from each customer column to the V3 field it fed.

## 3. Treat time windows as hard unless you price them

Model "between 9 and 12" as a hard `time_windows` entry only when the owner confirms service outside it is unacceptable.

Windows are hard by default and bound the start of service. A task that cannot start inside any window is left unassigned with constraint `time_window`. V3 prices lateness in three ways. `objective.per_late_hour` allows a late start on every window in the fleet, charged per hour late; omit it for hard windows, since an explicit `0.0` is a `400`. `time_windows[].per_late_hour` does the same for one task, which must then have exactly one window. `target` is a soft deadline on one task, independent of its windows, with its own `per_late_hour`. Early arrival always waits; the shift end plus `max_overtime_s` stays hard. Each rate has a maximum: a deadline that must hold is a hard window.

Deliver: each window type, hard or priced, and who confirmed it.

## 4. Choose `drop_cost` or `priority` deliberately

Decide per task: may the plan leave it out, or must it be served?

`drop_cost` is the only way to make a task optional: the solver serves it while that costs less than the fee, otherwise pays the fee and reports it as `priced_out`. `priority` (default `1`) ranks must-serve tasks: when not all fit, the solver maximises the summed priority of the served tasks before it looks at cost. It never makes a task droppable. Both on one task is a `400` `conflicting_fields`; `priority: 0` and a negative `drop_cost` are `out_of_range`.

Deliver: the rule you applied per task class, and where it came from.

## 5. Never fabricate or round a coordinate

Use the coordinates in the data, as `[longitude, latitude]`, at full precision. If a stop has no geocode, do not derive one from a town name or a neighbour: leave the task out and list it in the report.

A coordinate in `[latitude, longitude]` order lands in the wrong country: a request spanning more than one region is a `400` `invalid_value` at `/problem/locations`; otherwise every task comes back unassigned with constraint `unreachable`, as does a stop rounded off the road. A plausible but wrong coordinate gives no error at all: the solver plans a correct route to the wrong place.

Deliver: stops with a real geocode, and stops left out for lack of one.

## 6. Read `unassigned[].reasons` before you tune

When `summary.tasks.unassigned` is above `0`, read `unassigned[]` before you change a field or raise `options.time_limit_s`.

A `200` is not a feasibility verdict. Start with `category`: `impossible` means no vehicle can ever serve the task, so fix the input; `blocked` means it fits alone but not alongside the planned tasks; `priced_out` means its `drop_cost` was cheaper than serving it; only `time_limit` is fixed by more search time. Then read `reasons[].constraint` for the rule that refused it, and `relaxations[]` for the smallest change that would let it in. Report patterns, not tasks: "14 unassigned tasks all require skill `fridge`, which no vehicle has" is a finding. Before relaxing a rule, check capacity against demand and name the business rule you weaken.

Deliver: the unassigned list grouped by `category` and `constraint`.

## 7. Use `straight_line` for smoke tests only

Set `problem.travel.engine` to `straight_line` while you check that your mapping produces a valid request and a plan. Remove it before a dispatcher drives the plan.

`straight_line` uses great-circle distance at `travel.speed_kmh` (default `50`, from `1` to `1000`), with no road network. It needs no travel provider, so it suits CI, but rivers and motorways do not exist. The default engine, `osm`, uses free-flow road times (`tomtom` becomes the default when `travel.departure_time` is set); `speed_kmh` with any other engine is a `400`.

Deliver: the engine the delivered request uses.

## 8. Ask the operation owner on irreducible ambiguity

When the data does not say and the API has no safe default, ask the person who runs the operation instead of picking a value.

Typical cases: is a contractual window hard or priced, and which tasks may be dropped. Each is a business rule. If you default it, the plan encodes a rule nobody agreed to, and the API cannot warn you: the request is valid. Ask the questions in one round; while an answer is pending, state your assumption and keep it out of the request. The [requirements briefing](https://docs.solvice.io/guides/vrp/v3/requirements-briefing.md) lists the questions.

Deliver: each question with its answer and who gave it, or marked open.

## 9. Send no placeholder values

Send a field only when you have its value. Never fill one with `0`, `9999`, today's date or a made-up id to get past validation.

The API cannot tell a placeholder from a measurement. A `service_duration_s` of `0` makes every stop instantaneous; a `capacity` of `9999` switches the capacity rule off; neither returns an error. Most fields are optional: a job without `time_windows` can be served at any time in a shift, and a vehicle with no `capacity` field has no load limit. Omitting an unknown field is honest; inventing a value is a silent modelling decision.

Deliver: every omitted field, with the reason.

## 10. Deliver a modelling report

Hand back the request with a report; nobody can review a 2,000-line JSON body. First, a field-status table, one row per V3 field you set or deliberately omitted:

| Field | Value | Source |
| - | - | - |
| `vehicles[].capacity.weight` | `1200` | Data export, column `payload_kg` |
| `objective.per_late_hour` | `60.0` | Owner answer (dispatch lead) |
| `jobs[].service_duration_s` | `900` | Default, agreed with owner |

`Source` is data export (name the column), owner answer (name who) or default (name who agreed). Second, the unassigned list from the last solve, grouped by `category` and `reasons[].constraint`, plus the open questions from rule 8. Close with two or three sentences about what this plan showed, not a generic next-steps list.

## Reading order

1. [Integration paths](https://docs.solvice.io/guides/vrp/v3/integration-paths.md): which endpoints to call, in what order, for an integration versus a modelling job.
2. [API design](https://docs.solvice.io/guides/vrp/v3/api-design.md), [Constraints](https://docs.solvice.io/guides/vrp/v3/constraints.md) and [Errors](https://docs.solvice.io/guides/vrp/v3/errors.md) for the full detail.


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