Skip to main content
The solver distinguishes two kinds of rule:
  • Hard constraints define feasibility. A plan that violates one is invalid — the job is left unassigned rather than placed illegally.
  • Soft constraints are priced into the objective. They can be bent for a cost, so the solver trades them off against everything else.
The V3 schema is final, but capabilities land in phases. Rules marked Coming soon are accepted by the schema and currently return a 400 that names the field — never a silently-ignored constraint.

Overview

ConstraintKindDriven byStatus
Capacityhardvehicle.capacity · job.demandAvailable
Load compatibilityhardvehicle.incompatible_dimensionsComing soon
Time windowshardjob.time_windows (no cost fields)Available
Soft time windowssoftjob.time_windows.earliness_cost_per_hour / lateness_cost_per_hourComing soon
Shift hours & overtimehardshift.from/to, shift.max_overtime_sAvailable
Skills (hard)hardjob.skills[].namevehicle.skillsAvailable
Skills (soft)softjob.skills[].violation_costComing soon
Allowed / excluded vehicleshardjob.eligible_vehicles.allowed/excludedAvailable
Vehicle preferencessoftjob.preferences[].violation_costComing soon
Max distance / drive timehardvehicle.limits.max_distance_m / max_drive_time_sAvailable
Max stopshardvehicle.limits.max_tasksAvailable
Sequence (ordered)hardrelations: ordered (job ids)Available
Consecutivehardrelations: consecutiveAvailable
Same resourcehardrelations: same_resourceAvailable
Same routehardrelations: same_routeAvailable
Group sequencehardrelations: ordered (tags)Available
Same dayhardrelations: same_dayComing soon
Pickup & deliveryhardjob.pickup + job.deliveryAvailable
Driver breakshardshift.breaksComing soon
Synchronisationhardrelations: synchronizedComing soon

Capacity

Every vehicle has a multi-dimensional capacity; every job a demand. The load on a vehicle may never exceed its capacity on any dimension. Dimensions are named and matched by name, so weight, volume, pallets line up across jobs and vehicles regardless of declaration order.
{
  "vehicles": [
    {
      "id": "v1",
      "capacity": { "weight": 1000, "volume": 50 },
      "shifts": [{ "from": "2026-04-01T08:00:00Z", "to": "2026-04-01T17:00:00Z" }]
    }
  ],
  "jobs": [
    {
      "id": "t1",
      "location": { "coordinate": [4.35, 50.85] },
      "demand": { "weight": 30, "volume": 2 }
    }
  ]
}
A job whose demand names a dimension no vehicle declares is a 400 — you never get a silently-infeasible plan. The response carries load_after on every stop as a named map ({ "weight": 30, "volume": 2 }), matching the same dimension names.

Load compatibility

Coming soon. vehicle.incompatible_dimensions: [["general", "glass"]] — declares pairs of load dimensions that may not share a vehicle at the same time (the waste-collection “don’t mix streams” rule).

Time windows

A job may carry one or more time_windows. A hard window must be respected — if the vehicle can’t arrive within any window, the job is unassigned. Multiple windows mean “any of these slots is acceptable”.
{
  "id": "t1",
  "location": { "coordinate": [4.35, 50.85] },
  "time_windows": [
    { "from": "2026-04-01T09:00:00Z", "to": "2026-04-01T12:00:00Z" }
  ]
}
time_windows[].from / to
string (ISO 8601)
The allowed service window. Hard by default — arriving outside the window makes the job unassignable. Available.
time_windows[].earliness_cost_per_hour
number
Cost rate per hour of arriving early. When set, early arrival is allowed and priced instead of forbidden. (Coming soon — currently returns 400.)
time_windows[].lateness_cost_per_hour
number
Cost rate per hour of arriving late. When set, late arrival is allowed and priced instead of forbidden. (Coming soon — currently returns 400.)
A window with from == to and both cost fields set models a soft target time (penalised on both sides). The old target_arrival field is gone — this replaces it.

Shift hours & overtime

A vehicle works only within its shift windows. Each shift has a from/to; the route — including travel back to the end depot — must finish by to, unless max_overtime_s allows a costed extension.
"shifts": [
  {
    "from": "2026-04-01T08:00:00Z",
    "to":   "2026-04-01T17:00:00Z",
    "start": { "coordinate": [4.30, 50.80] },
    "max_overtime_s": 3600
  }
]
Multiple shifts on one vehicle model multi-day availability. Tasks with time windows spanning different shifts are automatically placed in the right shift.

Skills

Each job can require skills the serving vehicle must have. Matching is a subset check: job.skills[].namevehicle.skills.
{
  "vehicles": [{ "id": "v1", "skills": ["fridge", "lift"], "shifts": [/* … */] }],
  "jobs": [
    {
      "id": "t1",
      "location": { "coordinate": [4.35, 50.85] },
      "skills": [{ "name": "fridge" }]
    }
  ]
}
job.skills[].name
string
The required skill name. The serving vehicle must have this skill in its skills list. Available (hard).
job.skills[].violation_cost
number
When set, the skill becomes soft — assigning a vehicle that lacks it costs violation_cost instead of being infeasible. (Coming soon — currently returns 400.)

Vehicle eligibility

One object expresses every “which vehicle may serve this job” rule. Hard restrictions live in eligible_vehicles; soft preferences live in preferences.
"eligible_vehicles": {
  "allowed":  ["v1", "v2"],
  "excluded": ["v9"]
}
eligible_vehicles.allowed
array of strings
Hard whitelist — only these vehicles may serve the job. Omit to allow all vehicles. Available.
eligible_vehicles.excluded
array of strings
Hard blacklist — these vehicles may never serve the job. Mutually exclusive with allowed. Available.
preferences
array
Soft vehicle affinity: [{ "vehicle": "v2", "violation_cost": 800 }]. Serving a non-preferred vehicle costs violation_cost against the objective. (Coming soon — currently returns 400.)

Route limits

Per-vehicle caps on the shape of a route, grouped under limits. All hard.
"limits": {
  "max_distance_m": 250000,
  "max_drive_time_s": 28800,
  "max_duty_time_s": 36000,
  "max_tasks": 120
}
limits.max_distance_m
integer
Total route distance cap, metres. Available.
limits.max_drive_time_s
integer
Total driving time cap, seconds (excludes service & wait). Available.
limits.max_duty_time_s
integer
Total duty time cap, seconds (includes service & wait). Available.
limits.max_tasks
integer
Maximum number of job stops on the route. Available.

Sequencing

Inter-job ordering and grouping are expressed through the relations array. Each relation references jobs either explicitly (job_ids) or by tag (group), but not both.
"relations": [
  { "type": "ordered",       "job_ids": ["a", "b", "c"] },
  { "type": "consecutive",   "job_ids": ["b", "c"], "ordered": true },
  { "type": "same_resource", "job_ids": ["f", "g"] },
  { "type": "same_route",    "job_ids": ["x", "y"] }
]
RuleMeaningStatus
ordered (job ids)Jobs appear in this order on one routeAvailable
ordered (tags)Tag groups appear in this order; order within a group is freeAvailable
consecutiveJobs are adjacent; ordered: true also fixes their orderAvailable
same_resourceJobs served by the same vehicle — continuity of care across daysAvailable
same_routeJobs on the same route in the same shiftAvailable
same_dayJobs on the same calendar dayComing soon
synchronizedTwo vehicles meet at a location within max_wait_sComing soon
job_ids entries are polymorphic — each string is a job id or a tag (which expands to all jobs carrying that tag).
Synchronisation (type: "synchronized") is the home-health “two carers, one visit” rule. It’s the hardest constraint for the engine — it couples two routes — and arrives in a later phase as a best-effort feature.

Shipments

A shipment is a pickup → delivery pair: a job with pickup + delivery legs instead of flat visit fields. The solver guarantees the pickup precedes the delivery on the same route, and the carried demand counts against capacity only between the two stops. Same-vehicle and ordering are implicit — never expressed as a separate relation.
{
  "id": "ship-7",
  "demand": { "weight": 50 },
  "pickup":   { "location": { "coordinate": [4.30, 50.80] }, "service_duration_s": 120 },
  "delivery": { "location": { "coordinate": [4.50, 50.90] }, "service_duration_s": 120 },
  "skills": [{ "name": "tail_lift" }]
}
Available for mandatory shipments with coordinate legs, hard skills, hard time windows, and (optionally) a single allowed vehicle. Droppable shipments, soft skills/windows, multi-vehicle eligibility, and depot-referenced legs are Coming soon.

Driver breaks

Coming soon. shift.breaks[]floating (flexible within a window) and fixed (a mandatory off-duty interval), with an optional drive/duty trigger.
Breaks are inserted into the route’s timeline and respect the surrounding travel and service. Used for lunch breaks, EU 561/2006 driving rules, and scheduled unavailability.

The V3 API Model

How the full request is structured.

API Reference

The POST /v3/routing/solve schema.