> ## Documentation Index
> Fetch the complete documentation index at: https://developers.kardinal.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Cost modeling

> Express cost in real currency with the cost object, use piecewise-linear cost functions, guarantee a minimum revenue per tour, cap a cost across the whole fleet, and build custom objectives.

> [!TIP]
>
> `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](/concepts/objectives#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](/guides/advanced-constraints#tolerated-capacity-overflow) 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:

```json theme={null}
{
  "resources": [
    {
      "id": "subcontractor-van-1",
      "capacities": { "weight": 800, "deliveredUnits": 500 },
      "cost": {
        "costsByCapacity": {
          "deliveredUnits": { "constantCost": 200, "costFloor": 50, "costCoeff": -4 }
        }
      }
    }
  ],
  "orders": [
    {
      "id": "order-1",
      "stops": [
        { "type": "single", "id": "delivery-1", "position": { "lon": 2.3522, "lat": 48.8566 }, "kind": "delivery", "capacities": { "weight": 12, "deliveredUnits": 1 } }
      ]
    }
  ],
  "objectives": ["maximizeMandatoryStops", "minimizeCosts"]
}
```

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:

```json theme={null}
{
  "objectives": [
    {
      "type": "custom",
      "name": "minimizeFuelCost",
      "direction": "minimize",
      "costsByResourceTag": {
        "subcontractorA": { "km": { "costCoeff": 0.18 } }
      }
    }
  ]
}
```

`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:

```json theme={null}
{
  "resources": [
    {
      "id": "van-1",
      "cost": {
        "costsByStopTag": {
          "standard": { "costCoeff": -30 },
          "premium": { "costCoeff": -63 }
        }
      }
    }
  ],
  "orders": [
    { "id": "order-1", "stops": [{ "type": "single", "id": "stop-1", "position": { "lon": 2.3522, "lat": 48.8566 }, "tags": ["standard"] }] },
    { "id": "order-2", "stops": [{ "type": "single", "id": "stop-2", "position": { "lon": 2.37, "lat": 48.84 }, "tags": ["premium"] }] }
  ],
  "objectives": ["maximizeMandatoryStops", "minimizeCosts"]
}
```

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.

> [!WARNING]
>
> 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`](#rebuilding-the-default-objective-list-with-customcost).

```json theme={null}
{
  "resources": [
    {
      "id": "subcontractor-van-1",
      "tags": ["subcontractor"]
    }
  ],
  "globalConstraints": [
    {
      "type": "maxCumulatedCost",
      "name": "subcontractor-budget",
      "maximum": 1500,
      "costsByResourceTag": {
        "subcontractor": { "using": 150, "km": { "costCoeff": 0.6 } }
      }
    }
  ]
}
```

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](/guides/handling-infeasibility#testing-a-stop-on-a-specific-resource-with-state-assignments). 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

* [Objectives and how they're ranked](/concepts/objectives#the-default-list) — where `minimizeCosts` sits in the default objective order.
* [Modeling advanced constraints](/guides/advanced-constraints#tolerated-capacity-overflow) — the existing `costsByCapacity` worked example for tolerated overflow.


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