Skip to main content
cost, minimizeCosts, and custom objectives are deliberately powerful, but only a minority of plans need them. Before reaching for this page, check whether a standard objective (minimizeDistance, minimizeWorkingDuration, minimizeResources, and so on) already expresses the trade-off you’re after — see Built-in objectives.

The cost object

A resource’s cost object accumulates several independent contributions into a single currency figure that the minimizeCosts objective then optimizes: using (a flat cost per resource used), workedHours, km, costsByStopTag, costsByCapacity, costPerCapacityPerTravelledKm, and costPerCapacityPerTravelledHour. These fields reformulate, in a real currency, the same underlying quantities that minimizeDistance, minimizeWorkingDuration, and minimizeResources already count in raw units (km, hours, vehicles). This isn’t a strict one-to-one equivalence — it’s a different objective, built on a monetary version of the same data, not a guaranteed substitute for the raw-unit objectives.

Piecewise-linear cost functions

Every cost field preceding this section except using accepts a CostFloorsAndCoeffs-shaped object: a constantCost base, a costCoeff rate applied past costFloor, and an optional overcostCoeff rate that replaces costCoeff past a second, higher overcostFloor (usually a higher rate, and 0 caps the cost) — the same shape as a salary with a base, a free allowance, and an overtime-style rate beyond it. Modeling advanced constraints already has a worked costsByCapacity example using this object, to make capacity overflow on delivery-only tours economically unattractive rather than infeasible. costFloor is the threshold below which costCoeff doesn’t apply; constantCost is a fixed amount charged regardless of it — so a guaranteed minimum comes from constantCost, not from costFloor. costsByCapacity works on any capacity the stops carry (weight, revenue, a counter such as nbDeliveries…); the resource doesn’t need to declare it. The cost is computed on the quantities handled at the tour’s stops, not on the vehicle’s load: each stop adds the absolute value of its quantity for that capacity, whatever its kind or sign. Loading at departure isn’t a stop and doesn’t count, so a delivery counts once; a pickup and a delivery of the same 100 units count 200. With stopTags, only stops carrying one of those tags count. costFloor and overcostFloor then apply once, to that tour total. On a delivery-only tour, the total equals the load at departure.

Guaranteeing a minimum per-tour revenue via costsByCapacity

Create a capacity that only counts deliveries rather than any real load — for example deliveredUnits. The cost doesn’t need it declared on the resource; declare it there with a ceiling high enough never to bind only if you want to read it back in the tour’s filledCapacities values. Set it on every delivery stop (1 to count stops, or the number of units delivered there) and on no other stop, then put a constantCost/costFloor on that capacity so a tour that ends below the threshold keeps paying a flat penalty:
With these figures, a tour pays a flat 200 up to 50 delivered units; past that, each extra unit lowers the cost by 4, so it reaches 0 at 100 units. Nothing stops it there: past 100 units the contribution keeps falling below zero and acts as a bonus, so size costCoeff and costFloor accordingly.

Rebuilding the default objective list with customCost

A CustomObjective (type: "custom") lets you optimize a cost expression built from costsByResourceTag instead of a built-in ObjectivesEnum entry — for example scoping a custom fuel-cost objective to just the resources carrying a given tag:
name must not collide with a built-in ObjectivesEnum value (the schema enforces this). costsByResourceTag maps a Cost-shaped object to a resource tag, so a custom objective can apply to a subset of the fleet rather than every resource.

Modeling revenue or margin per stop

A negative costCoeff under costsByStopTag represents a revenue (a negative cost) rather than a charge, combined with minimizeCosts. It sits in the resource’s cost. The base is the number of tagged stops the tour visits, so each visited stop carrying that tag adds costCoeff once:
Here van-1 earns 30 for serving stop-1 and 63 for serving stop-2. Because maximizeMandatoryStops comes first, this revenue never makes the engine serve fewer mandatory stops. The revenue is included in tourCost, so it lowers tourCost, which can become negative when the resource has no other costs. If you report tourCost as money, take the revenue out of it first.
If you’d rather reason entirely in positive revenue figures, use a CustomObjective with direction: "maximize" and positive costCoeff values instead of negating them under the built-in minimizeCosts — there is no built-in maximizeCosts objective in ObjectivesEnum, only minimizeCosts. The two approaches aren’t interchangeable as they stand. objectives is a strict priority order, so put the custom objective at the exact position minimizeCosts held (third in the default list, after maximizeMandatoryStops and minimizeDelay); anywhere else, the engine trades revenue off against different objectives and can return a different plan. Also adjust whatever thresholds you’ve set elsewhere to the sign change.

Capping a fleet-wide cost with maxCumulatedCost

The Cost object also drives a constraint, not only minimizeCosts. A maxCumulatedCost entry in the plan’s globalConstraints adds up the cost written in its costsByResourceTag over every resource that carries the tag, across the whole fleet, and caps that total at maximum. The costsByResourceTag map works as described in Rebuilding the default objective list with customCost.
Here the combined spend on subcontractor vehicles, 150 per vehicle used plus 0.6 per kilometre driven, stays at or under 1,500. The cost is set on the constraint itself, so it does not depend on each vehicle’s own cost, and it is not added to tourCost. With only "using": 1, the same constraint caps the number of vehicles in use. The wildcard "*" is not accepted, so every resource the cap must count has to carry the tag. To cap the whole fleet, give every resource a common tag. A tag that no resource carries, for example one with a typing error, counts nothing. The API still accepts the plan, but its response carries a warning for that tag, in warnings, whose properties.path points to the tag, for example plan.globalConstraints.0.costsByResourceTag["subcontractors"]. Read it from the response to the POST or PUT that sent the plan: warnings aren’t stored with the plan, so a later GET doesn’t return them. If none of the cap’s tags matches a resource, the cap has no effect. When the cap is too tight, the engine first moves work to vehicles outside the tag, if it can. Otherwise it leaves stops unplanned, mandatory ones included, and nothing in the solution names the cap as the cause: globalViolations stays empty. To confirm the cause, lock the tours as described in Handling infeasibility. A broken cap then appears in globalViolations, with the counted resourceIds and an exceededCost that is the total cost counted, not the excess over maximum. maximum is required and has no default value. The API rejects a maxCumulatedCost entry without it: the response is a 400 with an INVALID_VALUE error, whose properties.path points to the missing field, for example plan.globalConstraints.0.maximum.

See also