Skip to main content
V3 is in beta. General availability (GA) is planned for January 2027.
This page maps request fields to the rules they switch on. A hard rule decides feasibility: a task that cannot be placed without breaking one comes back in unassigned[]. A soft rule is priced into summary.score.cost and traded off against travel. For how the pieces of a request fit together, see The V3 API model. For how soft costs are ranked, see Objective function.
A field the schema does not define is rejected with HTTP 400 and errors[].code: "unknown_field". A field the schema defines but the solver does not honour yet is rejected with HTTP 400 and errors[].code: "not_supported"; these are marked Not supported yet below. Each error item names the offending value with a JSON pointer, for example /problem/vehicles/0/shifts/0/breaks/1. See Errors.

Overview

Capacity

A vehicle declares a capacity per named dimension; a job declares a pickup_load, a delivery_load or both, and a shipment declares a load. The load never exceeds capacity in any dimension at any point along the route.
Dimensions are matched by name (weight, volume, pallets, and so on). Values are non-negative integers; a negative value is a 400. A request may use at most four distinct dimension names (see Limits). Check dimension names for typos, because a mismatch is not an error:
  • A vehicle whose capacity omits a dimension has capacity 0 in that dimension, so a job demanding it cannot ride that vehicle.
  • An empty object, "capacity": {}, omits every dimension: the vehicle carries nothing that has a pickup_load or a delivery_load.
  • A vehicle with no capacity field at all has no load limit in any dimension. To mean “no limit”, omit the field; do not send {}.
  • A shift’s own capacity replaces the vehicle’s in each dimension it names; the other dimensions keep the vehicle’s value, or no limit when the vehicle sets no capacity.
A task that no vehicle can carry comes back in unassigned[] with capacity. The response reports load_after on each stop and load_peak on each route, as the same named map.

Deliveries from the depot

jobs[].delivery_load is a quantity that leaves the vehicle at the visit, having been loaded at the depot. The vehicle starts its trip carrying the sum of the trip’s delivery_load quantities. Set pickup_load on the same job for what it collects there, so one visit can both drop off and pick up:

Loading and unloading different amounts at one address

A shipment carries one quantity from its pickup to its delivery. A swap that collects one quantity and drops off another at the same address is one job with both pickup_load (picked up) and delivery_load (dropped off), as above.

Time windows

A job or shipment leg carries one or more time_windows. This is a jobs[] entry with one window:
Windows are hard by default and bound the start of service: a task whose service cannot start inside any of its windows is left unassigned with time_window. Several windows mean “any of these is acceptable”. A vehicle that arrives before from waits. The start is measured from the arrival plus any windowed break taken on arrival at the stop, including the idle until that break’s window opens, so a break that pushes the start past to misses the window. A task without time_windows can be served at any time in a shift. A window whose from is after its to is a 400 with conflicting_fields. Write every timestamp with a UTC offset or Z. A timestamp without one is a 400 that names the field. Four fields price timing instead of forbidding it:
number
A flat cost for serving the task in this window rather than another of its own windows. Every window stays hard. Needs at least two windows on the task, and once any of them has a non-zero cost the task’s windows must not overlap. A non-zero choice_cost anywhere in the request cannot be combined with objective.per_late_hour (400). From 0 to 21474.83647. Reported in score.components.window_choice.
number
Fleet-wide: starting service after to becomes allowed and priced per hour late, on every window of every task that does not set its own rate. It applies to task windows only; the shift end (plus max_overtime_s) stays a hard limit. Omit the field for hard windows: an explicit 0.0 is a 400. At least 0.0006 and at most 1288490. Reported in score.components.lateness and per stop as lateness_s.
number
Per window: makes this window’s late side allowed and priced at this rate, replacing the fleet rate for the task and working whether or not a fleet rate is set. Hard and soft windows can therefore share one request. Must be at least 0.0006 and at most 1288490, and the task must have exactly one window. Early arrival still waits. Lateness is measured from the same instant as a hard window: the arrival plus any windowed break taken on arrival.
object
A soft deadline on one task, independent of its windows: { "at": "2026-04-01T10:00:00Z", "on": "arrival", "per_late_hour": 60.0 }. on is arrival (default) or departure (the end of the visit). Reaching that instant after at is priced per hour and never makes the task infeasible; arriving early never forces a wait. per_late_hour must be > 0 and at most 21474.83647. On a shipment, target is measured on the delivery leg. A leg’s own pickup.target or delivery.target sets a target on that leg; target with delivery.target is a 400. Reported per stop as target_lateness_s and in score.components.target.
A price above its maximum is a 400, so a soft rule cannot be made “almost hard” with a very large number. For a deadline that must hold, use a hard window.

Shift hours and overtime

A vehicle works only inside a shift’s from and to. The whole route, including the return leg and any unloading at the end depot, must finish by to unless max_overtime_s grants an extension. This is a shifts[] entry:
Every shift needs an id of 1 to 128 characters, unique within its vehicle. It is echoed as routes[].shift in the response. Several shifts[] on one vehicle model availability over several days. A shift whose to is before its from is a 400 with conflicting_fields. With max_overtime_s set (>= 0), to becomes a soft end and to + max_overtime_s the hard one. Time worked past to is reported as routes[].time.overtime_s and priced at per_overtime_hour, resolved per route: the shift’s costs, else the vehicle’s, else objective.costs. score.components.overtime is 0.0 unless one of them sets the rate. A synchronized or time_lag hold never runs into overtime: it stops at to.
boolean
default:"false"
The leg from start to the first task costs nothing: it is left out of the objective, the schedule and the distance and duration totals. The start stop is still reported. Requires start: true without one is a 400 conflicting_fields.
boolean
default:"false"
The same for the leg from the last task to end; requires end.
boolean
default:"false"
The shift starts at its first task: the leg from start takes no time, so the first arrival is from plus any start handling. The leg’s distance is still charged at per_travel_km and reported, so vehicles that start closer to their work stay cheaper. Its time is not charged and does not count toward max_route_duration_s, break triggers or max_leg_travel_s. Requires start, and cannot be combined with free_start_travel: either is a 400 conflicting_fields.

How days are counted

Three rules on this page refer to a day: the same_day relation, recurring period limits and the objective.per_delay_workday ASAP term. Each one assigns a whole shift to a day by the shift’s from. None of them looks at the time of an individual stop, so a stop served after midnight belongs to the day its shift started on. A quotas[] window is different: it counts each stop by its own service start. All three read the calendar the same way: a day is the calendar date of a value in the UTC offset it is written with. The offset a from is written with decides its day, so write it in the offset of the place the shift runs in. These two shifts[] entries are both on 1 April at +10:00:
early starts at 2026-03-31T22:00:00Z and late at 2026-04-01T03:00:00Z, but both are written with the date 1 April at +10:00, so:
  • same_day treats them as the same day. Two members of one same_day relation can be served one on early and one on late.
  • A period with every: "day" puts both shifts in 1 April and applies one cap to the two together.
Writing early as "from": "2026-03-31T22:00:00Z" keeps the same instant but moves the shift to 31 March for both rules: same_day no longer pairs it with late, and a daily period counts it on 31 March. There is no soft per-task day preference: preferred_date and objective.non_preferred_date_cost are a 400 (unknown_field). To keep a task on one day, limit its time_windows to that day. To pull work onto earlier days instead, set objective.per_delay_workday.

Skills

A job lists the skills it needs; the vehicle that serves it must hold every one of them.
ann can serve boiler-7; bob cannot.
  • Skills are objects on both sides. A bare string such as "gas" is a 400 with errors[].code: "invalid_type".
  • A job requirement may add min_level (>= 1, default 1). A vehicle entry may add level (>= 1, default 1). The vehicle qualifies when its level is at least the job’s min_level.
  • A skill name listed twice in one vehicle’s or one shift’s skills is a 400: give one level per skill.
  • A vehicle that omits skills can serve only jobs that require none.
  • A shift’s own skills replace the vehicle’s for that shift; [] means the shift provides none.
  • Skills are always hard. A job whose requirement no vehicle meets is returned in unassigned[] with required_skills.

Tags, serves_tags, and preferred_tags

Tags describe zones and territories rather than capabilities. Where skills require every listed entry, tags match on any overlap.
shifts[].serves_tags is hard: when it is set and non-empty, the shift serves only tasks that carry at least one of the listed tags. Untagged tasks can be served by every shift. A tagged task that no shift serves is returned in unassigned[] with serves_tags. vehicles[].preferred_tags is soft: serving a task that lacks a preferred tag costs that entry’s violation_cost, which is 0 or from 0.00001 to 21474.83647. Entries apply independently, so a task lacking several preferred tags is charged for each, and one vehicle’s entries must sum to at most 21474.83647. The total is reported in score.components.tag_preference.

Vehicle eligibility

Two fields decide which vehicle may serve a task, and a third prices the choice. These two jobs[] entries use the first two:
t1 may ride v1 or v2, and costs 15.0 more on v1 than on v2. t2 rides v1 or is left unassigned. From hardest to softest:
  • eligible_vehicles is a hard whitelist (allowed) or blacklist (excluded). Set one, not both, and do not send an empty allowed list. A task whose lists rule out every vehicle is returned with eligible_vehicles. To restrict a task to one vehicle, list only that vehicle in allowed: any of its shifts may serve it. The search may still move such a task within that vehicle’s routes; to freeze stops that are already dispatched, use plan.routes[].locked_count.
  • preferences is soft. Each { vehicle, violation_cost } entry is priced on its own: a task that lists several preferred vehicles pays for every listed vehicle that did not serve it, even if another one did. A violation_cost is 0 to 21474.83647, and the entries of one task must also sum to at most 21474.83647. Do not combine a preference with an allowed list that leaves out the preferred vehicle: the preference can never be met and its cost is paid on every solve.
  • assignment_costs is soft too, and prices the other side: each { vehicle, cost } entry is charged once when that vehicle (on any of its shifts) serves the task, and a vehicle not listed costs 0. A task rated expensive on every vehicle is the first one dropped when not everything fits. A cost is 0 to 21474.83647.
Preferences are reported in score.components.preferences and assignment costs in score.components.assignment. For a rule that must always hold, use eligible_vehicles instead of a large violation_cost. Common 400 cases: a vehicle id that matches no vehicle, in any of these fields; allowed and excluded set together; an empty allowed list; a vehicle listed twice in preferences or assignment_costs; a violation_cost or cost that is negative or above the maximum. The same fields are available on shipments[], where they apply to the pair.

Priority and optional tasks

Every task is must-serve unless it prices its own drop. Two fields control what happens when not everything fits; these are two jobs[] entries:
integer
default:"1"
A rank among must-serve tasks, from 1 to 4294967295. When not all of them fit, the solver keeps the summed priority of the served tasks as high as possible before it looks at any cost. With every task at the default this means serving as many as possible. It is a rank, not money, and never makes a task droppable. The summed priority of the must-serve tasks left out is reported as summary.score.unassigned_priority.
number
What leaving the task unserved costs, in the same currency as objective.costs, from 0 to 1,000,000,000. This is the only way to make a task optional: it is served while serving it costs less than the fee. Fees the plan chose to pay are reported in score.components.drop_costs. On a shipment the fee applies to the pair and is charged once. A task dropped for its fee is listed in unassigned[] with category: "priced_out".
Invalid combinations, each a 400: priority: 0; priority together with drop_cost; a negative drop_cost; drop_cost on any task while a positive objective.per_delay_workday is set (not_supported). A one-vehicle allowed list does not make a task must-serve: a task restricted that way that does not fit still comes back in unassigned[], and pays its drop_cost if it has one.

Route limits

limits sits on the vehicle, and a shift can override max_distance_m, max_route_duration_s and max_stops in its own limits. This is a vehicles[] entry:
All of them are hard and apply to each route separately, and each is absent (no cap) unless set. max_route_duration_s caps the whole route span, the same quantity as the response’s routes[].time.span_s: travel, service, waiting and depot handling all count. max_stops counts stops: a job is one, a shipment two; start, end, break and reload stops in routes[].stops do not count. max_distance_m and max_route_duration_s must be 0 to 2,147,483,647 at either level (out_of_range otherwise), and max_stops an integer from 0 to 2,147,483,647, as in limits.periods and quotas. max_leg_travel_s caps any single travel leg instead of the whole route: the travel_s reported on each stop, including the leg from the start to the first task and from the last task to end. Only travel counts (setup, service and waiting do not), and speed_factor applies first. A leg made free by free_start_travel or free_end_travel, or absent on an open start or end, is exempt. It is set on the vehicle only, and must be between 1 and 2,147,483,647: 0 is a 400 with out_of_range, not “no cap”. A task kept out by max_distance_m, max_route_duration_s or max_stops is returned in unassigned[] with max_distance, max_route_duration or max_stops, and one kept out by max_leg_travel_s with max_leg_travel. The leg cap is enforced as reachability: no route may use a leg over the cap, as with a leg that has no road connection, but the two are reported apart. On /evaluate, a leg over the cap reports a max_leg_travel violation: on the stop it leads to, where the message gives the leg’s travel time and the cap, or on the route for the leg to end. A leg with no road reports unreachable instead.

Period limits

limits.periods[] caps a vehicle’s totals across all of its shifts in a period. This is a limits object:
A period is either recurring, every: "day" or every: "week" (weeks run Monday to Sunday), or one explicit from/to window with to exclusive. Each shift counts toward the one period that contains its from. For a recurring period the date is read in the UTC offset that from is written with; see How days are counted. Set exactly one of every or from + to, and at least one maximum, each 0 or more. A from/to window must end after it starts and contain at least one of the vehicle’s shift starts; a period inside a single shift is not supported (not_supported). Anything else is a 400. A recurring period in which none of the vehicle’s shifts start is skipped. The third entry above blocks the vehicle on 3 April: max_stops: 0 for the shifts that start that day. A task kept out by a period’s max_route_duration_s, max_distance_m or max_stops is returned in unassigned[] with period_limit, the code /evaluate uses: the period leaves each of its shifts that much less room. Where the vehicle’s own limit is what the task would breach, the reason is that limit’s code instead. On /evaluate, a plan over a period’s cap reports one period_limit violation, on the route of the period’s first used shift.

Quotas

quotas[] is a top-level field of problem. Each entry caps how many stops of the listed tasks the listed vehicles serve together, for example a contractor pool that shares one install quota. A quota with one vehicle is a per-vehicle cap on those jobs. This is a quotas array:
  • id, vehicles, tasks and max_stops are required. tasks takes job and shipment ids.
  • from and to are optional and go together. A stop counts when its service starts in [from, to) (arrival + wait_s in the response when no windowed break is taken on arrival there); no calendar day is involved, and a window can sit inside one shift. Without a window, every stop counts.
  • Stops count as max_stops counts them: a job is one stop, a shipment two.
  • The cap is hard: a job over it is left unassigned with quota.
  • On /evaluate, a plan over a quota reports one quota violation, on the route of its first used member shift.
Common 400 cases: a duplicate quota id; an empty vehicles or tasks list; an unknown vehicle, job or shipment id; an id listed twice in one list; a window with only one end; a from or to that is not a timestamp; a to that is not after from; and a window that overlaps no shift of the quota’s vehicles.

Relations

relations[] ties tasks to each other. There are ten types, and all ten are hard: a relation has no cost field. This is a relations array:
  • sequence: members that share a route appear on it in the listed order; other stops may sit between them. It does not by itself put the members on the same route. Members are listed in stops. A single entry in stops pins that job as the first stop of its route.
  • consecutive: like sequence, and no other stop may sit between two neighbouring members that are served on the same route (V2 DIRECT_SEQUENCE). If a member is unassigned it is skipped, so only listed neighbours that are both served are adjacent. Members on different routes are unconstrained: add same_route over the same stops to keep them together. A stop has one successor and one predecessor across all consecutive relations ([a, b] and [b, c] may overlap, [a, b] and [a, c] are a 400), and stops needs at least two entries. To keep a shipment’s two legs back to back, list x:pickup and x:delivery. Name a chain in an all_or_none relation to serve it whole or not at all.
  • tag_order: orders tasks by tag instead of by id. tags lists at least two tags; on every route, every task tagged tags[0] precedes every task tagged tags[1], and so on. Tasks carrying none of the listed tags may sit anywhere, and a listed tag no task carries is accepted. A tag that selects a shipment is a 400. A breach is reported as tag_order.
  • same_vehicle: members are served by the same vehicle, on any of its shifts. Select members with stops or with one tag, not both.
  • same_day: members are served on shifts whose from falls on the same calendar date, read in the offset each from is written with. The day belongs to the shift, not to the time of the stop; see How days are counted. Select members with stops or with one tag, not both.
  • same_route: members are served on one route: one shift of one vehicle (V2 SAME_TRIP). Reloads within the shift do not split it. Select members with stops or with one tag, not both. Jobs only: a shipment named in stops or carrying the tag is a 400.
  • synchronized: members start within max_spread_s seconds of each other, on any vehicles. max_spread_s is required and >= 0; 0 means exactly together. Add "on": "departure" to synchronise the ends of service instead of the starts. Select members with stops, up to eight members. The schedule holds an early member back; the hold is reported on the stop as hold_s.
  • time_lag: a directional gap between two jobs on any vehicles. The time from from to to must lie between min_s and max_s seconds. Each end is a stop plus an optional on, which is service_start (default) or departure. min_s defaults to 0, an absent max_s means no upper bound, and both may be negative. from.stop and to.stop must differ, max_s must not be below min_s, and both ends must be jobs: a shipment id or a shipment leg is a 400. Chain more than two jobs with one relation per consecutive pair.
  • compatible_vehicles: every two different vehicles that serve the members must be compatible under vehicles[].compatible_with. A vehicle without the field pairs with any vehicle; a list is a veto (neither list may exclude the other); a vehicle always pairs with itself, so list only its own id to pair it with no other. Members are stops only, 2 to 16, each also named by a synchronized or time_lag relation. Tasks left without a compatible placement are unassigned.
  • all_or_none: either every member is served or none is. It sets no timing of its own. Members are stops, at least two; a member may be a shipment id, which stands for both legs, and a shipment-leg id is a 400 (name the shipment). Members are coupled by a synchronized or time_lag relation, or the group’s stops (a shipment counted as its two legs) are exactly one consecutive chain with no member coupled by synchronized or time_lag, directly or through a sequence or consecutive relation. The chain form keeps a shipment’s legs and the jobs between them together: a tow x:pickup, a job t and x:delivery, chained with consecutive and kept on one route with same_route, is served whole or not at all. Overlapping relations chain: one on A and B plus one on B and C serves all three or none of them. The rule is switched off for a whole group, chained members included, once one of its served members sits inside a plan route’s locked_count or, on solve, is a pinned plan stop. A member that could be served but is held back because another cannot is unassigned with the reason all_or_none. On /evaluate, a plan that serves only some members reports an all_or_none violation on each served member.
same_vehicle, same_day, same_route, time_lag and compatible_vehicles constrain the members that end up assigned: an unassigned member imposes nothing on the others. Common 400 cases:
  • a task id that does not exist, or a tag on same_vehicle, same_day or same_route that no task carries
  • a relation with no member selector, or with two of them (stops together with tag)
  • a synchronized relation without max_spread_s, with a negative one, or with more than eight members
  • a job listed twice in one sequence, same_vehicle, same_day, same_route or synchronized relation
  • on: "arrival" on a synchronized relation or a time_lag end (service_start and departure are the only values there)
  • synchronized and time_lag relations, and the sequence relations joining them, that link more than 16 tasks into one group (see Limits)
  • a job listed in more than one same_vehicle relation, more than one same_day relation, more than one same_route relation, or more than one synchronized relation (merge them into one); a job may sit in any number of sequence and time_lag relations
  • more than one tag_order relation, fewer than two tags, a tag listed twice, or a task that carries more than one of the listed tags
  • a time_lag whose two ends are the same job, whose max_s is below min_s, or that names a shipment
  • a same_route relation that names or selects a shipment
  • a shipment leg coupled to other tasks by a synchronized or time_lag relation, directly or through a sequence with a member that is
  • a sequence that puts a delivery before its own pickup, or names a delivery alone
  • a compatible_vehicles relation with fewer than 2 or more than 16 stops (invalid_value and limit_exceeded), that names a shipment, or that names a job which no synchronized or time_lag relation also names (both not_supported)
  • an all_or_none relation with fewer than 2 stops (invalid_value), that names a shipment leg, that names a shipment or a job outside a coupled group or a single consecutive chain, whose chain runs on past the group, or whose chain member is coupled by synchronized or time_lag, directly or through a sequence or consecutive relation (all not_supported)
  • a vehicles[].compatible_with that is empty (invalid_value) or names a vehicle that does not exist (unknown_reference)
  • an unrecognised type, reported with errors[].code: "invalid_type"

Shipments in relations

To relate a shipment, name one of its legs by its stop id: the leg’s pickup.id or delivery.id, by default "{shipment}:pickup" and "{shipment}:delivery". The relation then binds that leg like a job. ["s1:pickup", "j1", "s1:delivery"] in a sequence serves j1 between the two legs. A leg cannot be coupled across routes: a leg named by a synchronized or time_lag relation, or in a sequence with a member that is, is a 400.
Only all_or_none takes a shipment id, and only in a group that is one consecutive chain. Every other relation type rejects a shipment id with a 400, so a shipment is never silently dropped from a relation; name its legs instead.
A shipment’s own rule, delivery after pickup on one vehicle, needs no relation. To tie a shipment to a vehicle, use its eligible_vehicles.

Shipments

A shipments[] entry is a pickup and a delivery served by one vehicle:
The delivery is always placed after the pickup on the same route. That ordering is built in, so it needs no relations[] entry. load is carried on the vehicle between the two legs. Each leg accepts its own id, location (required), setup_duration_s, service_duration_s, time_windows and target. Load is the shipment’s own load, not a per-leg field. A leg id defaults to {shipment}:pickup or {shipment}:delivery and is what the response reports on that stop. A leg id, set or defaulted, that equals any job, shipment or other leg id is a 400 with duplicate_id. drop_cost, priority, required_skills, tags, eligible_vehicles, preferences, assignment_costs and service_factors sit on the shipment and apply to the pair, priced once. target is measured on the delivery leg. A leg’s own pickup.target or delivery.target sets a target on that leg; target with delivery.target is a 400.

Driver breaks

Breaks are listed in shifts[].breaks. Each has a type: windowed, triggered or fixed. This shifts[] entry carries three of them. Each may take an optional id, which comes back on its break stop:
All breaks are hard, and break time is not priced separately.

Break in a time window

A windowed break lasts duration_s seconds and must start no earlier than the window’s from and no later than its to. It is taken wherever the vehicle is at that moment. It takes exactly one time_window.
When a stop’s service would run into the break’s window, the break is taken on arrival at that stop, before its service; a vehicle that arrives before the break’s from idles until it. The stop’s time window then bounds the start of service after the break. With a 09:00–09:05 window, an arrival at 08:02 and a break taken 10:00–10:30 on arrival, service starts at 10:30: the stop misses its window, so a hard window leaves it unassigned and /evaluate reports a time_window violation, and lateness_s is 5100 (10:30 − 09:05). The break stop is listed before the stop it was taken at, so that stop’s arrival can precede the break’s. A windowed break has no location: sending one is a 400 (not_supported, pointer .../breaks/{k}/location), because the break is always taken where the vehicle is.

Break after driving time

A triggered break with after: "drive" is taken once the driving time since the last break, or since the start of the shift, exceeds threshold_s. The count starts again after the break. This is the EU 561/2006 rule of 45 minutes after 4 h 30 of driving:

Break after duty time

With after: "duty" the count is driving time plus service time. This break is taken after six hours of work:

Fixed off-duty interval

A fixed break is an interval with a set from and to. No stop is served inside it: a stop whose service would not finish before from is served from to instead, except the service of a job with break_interruptible: true (below), which pauses for the break. Travel is not split around it, so a drive still under way at from overlaps the break, and the overlap counts in both time.travel_s and time.break_s. It takes those two fields only: no duration_s, no location.

Breaks during a long job

By default a break never falls inside a job’s service: the whole service is moved after the break, or the break is taken before the job starts. Set break_interruptible: true on a job whose work may pause. A fixed, windowed or duty-triggered break that falls inside its service then pauses it, and the work resumes when the break ends. The job ends the break’s length later. A drive-triggered break never pauses a job, because it counts driving time only. Shipment pickups and deliveries cannot be interrupted.
A six-hour job started at 08:00, with a fixed break from 12:00 to 13:00, works until 12:00, pauses, and ends at 15:00. Without the flag the job has to end by 12:00 or start at 13:00.

Breaks in the response

A break appears on its route as a stop of type break, in sequence between the stops it falls between:
arrival is when the break starts, departure when it ends, and service_s its length in seconds. The stop also carries load_after when the request uses capacity. It carries the id of the request break when that break has one (breaks[].id, 1 to 128 characters, unique within the shift), and no location, also when the request set a location on the break. A break that pauses an interruptible job has no break stop of its own. The job stop reports the paused seconds in break_s, and its departure is that much later. The route’s time.break_s includes them.

Break rules

Common 400 cases:
  • duration_s or threshold_s that is not greater than 0, or a threshold_s above 2,147,483,647
  • a window or a fixed break whose from is after its to
  • a field the break type does not define, such as location on a fixed break (unknown_field)
  • a location on a windowed break (not_supported)
A triggered break has no location and no resets_accumulator: the count always starts again after a break, and the break is taken wherever the vehicle is. A shipment leg has no break_interruptible. Sending any of them, or a windows list on a windowed break, is unknown_field.

Multi-trip reloads

A vehicle may return to a depot mid-shift to empty or restock and start a fresh trip. Name the depots in the shift’s reloads:
  • reloads lists depots[].id values, each at most once. An unknown id, or one listed twice, is a 400.
  • Capacity is enforced per trip. Each reload appears in the response as a stop of type reload carrying the depot id.
  • max_trips caps trips, not reloads: trips = reloads used + 1, across all reload depots of the shift, not counting the final unload of unload_before_end (below). It must be at least 1; without it, trips are not capped.
  • Whatever max_trips says, one shift reloads at one depot at most eight times. List more than one reload depot to go beyond that. A plan you send whose route reloads at one depot more than eight times is a 400.
  • Each reload takes the depot’s service_duration_s and is charged its per_reload, reported in score.components.reloads.
  • shifts[].initial_load declares stock already on the vehicle at shift start. It occupies capacity on the first trip only and is cleared at the first reload. A value above the capacity in that dimension (the shift’s, else the vehicle’s) is rejected with a 400. At capacity the first trip has no room, so the route can start with a reload at one of the shift’s reloads to unload before its first task.
  • shifts[].unload_before_end: true keeps the vehicle from ending the shift loaded, as a waste truck must empty at a tip before returning to its yard. When the last trip carries load, the route ends with one more reload stop at the shift’s reloads depot the solver finds cheapest, then drives to end. This stop takes the depot’s service_duration_s and per_reload, and does not count toward max_trips. A route whose last trip carries no load ends without it. true without reloads is a 400. In a plan you evaluate, a route that ends loaded reports an unload_before_end violation, and a reload as the last stop is accepted on such a shift only.
  • depots[].max_intake caps what a depot receives across the whole request, per named dimension, as a tip site accepts only so much waste. Counted is the load on board on arrival at each reload there, including the final tip of unload_before_end, and the load on board at the end of a shift whose end is that depot (an unload_before_end shift unloads before its end instead). It is a total over every shift and day, not a daily limit. A dimension left out is uncapped, and 0 means the depot takes none of it. The solver sends trips to another of their reloads depots when one is full, and leaves a task unassigned (reason max_intake) when none has room. A cap on a depot no shift reloads at or ends at (without unload_before_end), a negative amount or an undeclared dimension is a 400. In a plan you evaluate, a depot over its cap reports one max_intake violation per dimension, with the excess, on the first route that unloads there.

Travel and service time

These fields do not forbid anything on their own, but they change how long a route takes, and so whether time windows and shift ends can be met.
number
default:"1.0"
Travel time is divided by this factor: above 1.0 is a faster vehicle, below is slower. Must be greater than 0 and at most 5.
number
default:"1.0"
Service time is divided by this factor: above 1.0 is a faster worker. Same range as speed_factor. shifts[].service_factor replaces it for one shift, and a task’s service_factors[].factor multiplies whichever applies. It scales a task’s service_duration_s only, never setup time, depot handling, waiting or breaks. V2’s durationModifier went the other way: it multiplied the duration (0.8 = 20% faster). When migrating, send 1 / durationModifier as the task’s service_factors[].factor.
array
On a job or a shipment: per-vehicle overrides for one task, as { "vehicle": "ann", "factor": 1.25 } entries. The factor multiplies the shift’s factor if the shift sets one, else the vehicle’s: scheduled service time = service_duration_s / (shift or vehicle factor × task factor). On a shipment it scales both legs. A vehicle not listed works at its own rate. Each factor must be greater than 0 and at most 5. An unknown vehicle id, or a vehicle listed twice, is a 400.
integer
On a job or shipment leg: time paid once on arrival at a location, on top of service_duration_s. It is skipped when the previous stop on the route is at the same location, so co-located stops share one setup.
The response reports the service time actually scheduled as stops[].service_s, which differs from service_duration_s whenever a factor applies.

Grouping

objective.grouping keeps nearby tasks together without restricting the plan. Tasks within radius_m of each other form a group, and every entry into a group from a task outside it pays per_entry, the first entry included. Entering from the shift’s start or from a reload is free. This is an objective object:
per_entry is required and must be between 0 and 1000. radius_m defaults to 180 and must be positive. distance_metric is straight_line (default) or road. The total is reported in score.components.grouping.

Reachability

When the road network has no connection between two locations, no route may use that leg. The rule has no request field of its own; a vehicle’s limits.max_leg_travel_s adds to it by ruling out every leg that takes longer than the cap. A task that can only be reached over a missing road comes back unassigned with unreachable: check its coordinate, which is [longitude, latitude]. One that can only be reached over a leg longer than the cap comes back with max_leg_travel: check the leg cap on the vehicles. See Distance matrices for how travel data is looked up.

What a broken hard rule looks like

A solve response never contains a violated hard rule. A task that cannot be placed is listed in unassigned[]:
  • category says what kind of problem it is: impossible (no vehicle can ever serve the task), blocked (it does not fit alongside the tasks the plan serves, or its all-or-none crew cannot be placed together), priced_out (its drop_cost was cheaper than serving it) or time_limit (a slot exists that the search did not use in its time budget).
  • reasons[] names every rule that refused the task on some vehicle, one constraint each. The sections above give the constraint for each rule.
  • relaxations[] suggests the smallest change that would let the task fit, when one exists.
The full category and reason-code tables, with what to do about each, are in Errors. Four things are specific to constraints:
  • Every rule, once. When different rules block different vehicles, each is listed: capacity on some vehicles and time windows on others is capacity and time_window. unknown means no rule was recorded.
  • A window no shift overlaps. The constraint is time_window with category impossible. Check the dates and UTC offsets of the window and the shifts.
  • Relations name themselves. same_vehicle, same_day and same_route narrow a task to the vehicle, day or route its already-placed partners use; the reasons list that relation, plus any rule that blocks the task there. A task kept out by sequence reports sequence.
  • priced_out and time_limit name no constraint. No rule kept the task out: their one reason names no constraint. Branch on category.
Each relaxations[] entry has five fields: The list is empty for rules with no such fix, for example a skills mismatch. To check a plan you built yourself, send it to /v3/routing/evaluate. There, broken hard rules are reported as violations[] on the stop or route instead.

The V3 API model

How the full request is structured.

Objective function

How the soft rules above are priced and ranked.

Errors

The 400 error body, item codes and pointers.

API reference

Every field of the request and response schemas.