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 or shipment declares a pickup. 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 or a delivery.
  • 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 task that no vehicle can carry comes back in unassigned[] with CAPACITY_EXCEEDED. A shift can override the vehicle’s capacity with its own shifts[].capacity, for example a van that carries less on a day it tows a trailer. Each dimension named on the shift replaces the vehicle’s value for that dimension; dimensions the shift does not name keep the vehicle’s capacity.
On tue the vehicle carries 600 weight and still 50 volume. 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 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 quantities. Set pickup 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 (picked up) and delivery (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: a task that cannot be reached inside any of its windows is left unassigned with TIME_WINDOW_VIOLATED. Several windows mean “any of these is acceptable”. A vehicle that arrives before from waits. 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 cost anywhere in the request cannot be combined with objective.per_late_hour (400). From 0 to 21474.83. Reported in score.components.window_choice.
number
Fleet-wide: arriving 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 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 > 0 and at most 1288490, and the task must have exactly one window. Early arrival still waits.
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 end (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.83. On a shipment the target is measured on the delivery leg. 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. With max_overtime_s set, 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 by per_overtime_hour, set fleet-wide in objective.costs and overridable per vehicle in vehicles[].costs or per shift in shifts[].costs; score.components.overtime is 0.0 unless a rate applies.
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. Has no effect unless start is set.
boolean
default:"false"
The same for the leg from the last task to end.

How days are counted

Three rules on this page refer to a day: the same_day relation, preferred_date, and recurring period limits. 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 task with preferred_date: "2026-04-01T08:00:00+10:00" is on its preferred day on either shift and pays no objective.non_preferred_date_cost.
  • 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 all three rules: same_day no longer pairs it with late, the task above pays non_preferred_date_cost on it, and a daily period counts it on 31 March.

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

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 TAG_RESTRICTED. vehicles[].preferred_tags is soft: serving a task that lacks a preferred tag costs that entry’s violation_cost, which must be 0 or more. Entries apply independently, so a task lacking several preferred tags is charged for each. The total is reported in score.components.tag_preference.

Vehicle eligibility

Four fields decide which vehicle may serve a task, or what it costs. These two jobs[] entries use them:
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:
  • locked_vehicle pins the task to one vehicle id. Only that vehicle may serve it, and re-optimisation never moves it to another. A pinned task that its vehicle cannot serve on any shift is returned in unassigned[] with COMMITMENT_BLOCKED.
  • 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 VEHICLE_EXCLUDED.
  • 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.83, and the entries of one task must also sum to at most 21474.83. Do not combine a preference with a locked_vehicle for a different vehicle: the preference can never be met and its cost is paid on every solve.
  • assignment_costs is soft and prices the vehicle that did serve the task, where preferences charges the ones that did not. Each { vehicle, cost } entry is an absolute price in the objective’s currency, charged once when that vehicle serves the task on any of its shifts. A vehicle with no entry costs 0, a cost is 0 to 21474.83, and a vehicle id that matches no vehicle is a 400. A task rated badly on every vehicle is the first one dropped when capacity runs out. The charge is reported in score.components.other. A problem that sets assignment_costs is solved as a whole, not decomposed.
For a rule that must always hold, use eligible_vehicles or locked_vehicle instead of a large violation_cost. Common 400 cases: a vehicle id that matches no vehicle, in any of the three fields; a locked_vehicle that eligible_vehicles rules out; allowed and excluded set together; an empty allowed list; a vehicle listed twice in preferences; a violation_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, >= 1. 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.unserved_priority.
number
What leaving the task unserved costs, in the same currency as objective.costs. 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_fees. 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_fee.

Route limits

limits sits on the vehicle, and a shift can override max_distance_m, max_route_duration_s and max_tasks in its own limits. This is a vehicles[] entry:
All of them are hard and apply to each route separately. 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_tasks counts stops: a job is one, a shipment two. max_leg_travel_time_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 ignore_start_leg or ignore_end_leg, 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 or max_route_duration_s is returned in unassigned[] with VEHICLE_RANGE_EXCEEDED; one kept out by max_tasks with ALL_ROUTES_BLOCKED. The leg cap is enforced as reachability: a leg over the cap is treated like a leg with no road connection. A task that can only be reached over such a leg is returned in unassigned[] with UNREACHABLE_LOCATION, and its message speaks of a missing road connection and the coordinate even though the cap is the cause. On /evaluate, a leg over the cap reports an unreachable violation. On a stop, the message gives the leg’s travel time and the cap; on the route, for the leg to end, it reads as a missing road connection.

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. A from/to window must contain at least one of the vehicle’s shift starts; a period inside a single shift is not supported. Anything else is a 400. The third entry above blocks the vehicle on 3 April: max_tasks: 0 for the shifts that start that day. A task kept out by a period’s max_duration_s or max_distance_m is returned in unassigned[] with VEHICLE_RANGE_EXCEEDED; one kept out by a period’s max_tasks with ALL_ROUTES_BLOCKED. 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 job_ids 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, job_ids and max_tasks are required. job_ids 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); no calendar day is involved, and a window can sit inside one shift. Without a window, every stop counts.
  • Stops count as max_tasks counts them: a job is one stop, a shipment two.
  • The cap is hard: a job over it is left unassigned with ALL_ROUTES_BLOCKED.
  • 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 job_ids 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 nine types, and all nine 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.
  • 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. A tag that selects a shipment is a 400.
  • 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_wait_s seconds of each other, on any vehicles. max_wait_s is required and >= 0; 0 means exactly together. Add "on": "end" 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 start (default) or end of service. 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 only, at least two, each also named by a synchronized or time_lag relation. 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 has a locked_vehicle or sits inside a plan route’s locked_count. /evaluate does not check all_or_none: a plan that serves only some members reports no violation for it.
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_wait_s, with a negative one, or with more than eight members
  • a job listed twice in one relation
  • 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, or that names a job which no synchronized or time_lag relation also names (both 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.
A shipment’s own id constrains nothing. Most relations accept it and then ignore it, so the request returns 200 and the shipment is planned as if the relation were not there.
“Accepted and ignored” means the relation still binds its job members. A shipment’s own rule, delivery after pickup on one vehicle, needs no relation. To tie a shipment to a vehicle, use its locked_vehicle or 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 and time_windows. 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. drop_fee, priority, skills, tags, eligible_vehicles, locked_vehicle, preferences, service_factors and preferred_date sit on the shipment and apply to the pair. target is measured on the delivery leg.

Driver breaks

Breaks are listed in shifts[].breaks. Each has a type: windowed, triggered or fixed. This shifts[] entry carries three of them:
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. Give exactly one window.
A windowed break also accepts a location. It does not change the route: no travel to that place is planned, and the break is still taken between two stops of the route.

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. Everything on the route happens entirely before from or entirely after to. It takes those two fields only: no duration_s, no location.

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 has no id and no location, also when the request set a location on the break.

Break rules

Common 400 cases:
  • duration_s or threshold_s that is not greater than 0
  • 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)
Not supported yet (400, not_supported):
  • more than one windows entry on a break
  • an empty windows list on a windowed break
  • location on a triggered break
  • resets_accumulator: false (the count always starts again after a break)
  • break_interruptible: false on a job or shipment leg (the default true is accepted)

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 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. 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 cost_per_visit, reported in score.components.depot_visits.
  • 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.

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 overrides it for one shift. It scales a task’s service_duration_s only, never setup time, depot handling, waiting or breaks.
array
On a job or a shipment: per-vehicle overrides for one task, as { "vehicle": "ann", "factor": 1.25 } entries. The factor multiplies the vehicle’s or shift’s own factor: scheduled service time = service_duration_s / (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 and preferred dates

Two soft rules shape a plan without restricting it. objective.grouping keeps nearby tasks together. Tasks within radius_m of each other form a group, and each time a route leaves a group and comes back it pays cost_per_reentry. This is an objective object:
cost_per_reentry is required and must be between 0 and 1000. radius_m defaults to 180 and must be positive. distance is straight_line (default) or road. The total is reported in score.components.grouping. preferred_date on a job or shipment names the day it should be served on in a multi-day problem. It takes a full timestamp. Its calendar date, in the offset it is written with, is matched against the calendar date of each shift’s from in that value’s own offset, so passing the intended shift’s from is always correct, and a local midnight such as 2026-04-02T00:00:00+02:00 is 2 April. See How days are counted. Serving the task on another day costs objective.non_preferred_date_cost once; with that cost unset or 0 the field has no effect. A task is never left unassigned to honour its preferred date.

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_time_s adds to it by ruling out every leg that takes longer than the cap. A task that can only be reached through such a leg comes back unassigned with UNREACHABLE_LOCATION: check its coordinate, which is [longitude, latitude], and any 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), priced_out (its drop_fee was cheaper than serving it) or search_limit (a slot exists that the search did not use in its time budget).
  • reasons[].code names the rule. The sections above give the code 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:
  • One code per task. When different rules block different vehicles, the code names one of them, so treat it as the place to start looking. A mix that no single code describes is reported as ALL_ROUTES_BLOCKED, and capacity on some vehicles plus time windows on others as CAPACITY_AND_TIME_WINDOW.
  • A window no shift overlaps. The code is TIME_WINDOW_VIOLATED with category impossible. Check the dates and UTC offsets of the window and the shifts.
  • Relations have no code of their own. same_vehicle and same_day narrow a task to the vehicle or day its already-placed partners use, and the code then names the rule that blocks it there. When no other rule is involved, and for a task kept out by sequence, the code is ALL_ROUTES_BLOCKED.
  • priced_out and search_limit share the code SOLVER_LIMIT. Branch on category to tell them apart.
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.