Define a custom cost model for a route optimization run
Cost Model per Request
costParams is an optional top-level object on POST /api/v2/optimizations/scheduled. It defines a custom ROv3 cost model for the run, in place of the preset mode. This field is fully optional — omitting it preserves current behavior (either the preset mode you send, or the org's route optimization profile default).
Send exactly one of mode or costParams on a given request. Sending both, or neither, results in a 400 response.
| Name | Type | Description |
|---|---|---|
| costParams | object | Optional. A plain object of cost-model keys (see below), camelCase, all individually optional. Cannot be sent together with mode. |
Supported KeysAll keys are optional — send any subset. Each is converted to its LogisticsOS snake_case equivalent under the hood.
Must be
>= 0:
costPerOrder,costPerPickup,costPerDropoff,costPerUnitTime,costPerUnitDistance,costPerUnitLateTime,costPerOverorder,costPerUnitOvertime,costPerUnitOverdistance,overAdjacentDistanceCost,fixedCostMust be
> 0(threshold "start" values):
overorderStart,overtimeStart,overdistanceStart,overAdjacentDistanceStart
costParams Field Reference
| Field | Rule | Type | Description |
|---|---|---|---|
costPerOrder | >= 0 | Flat per-stop cost | Flat cost charged once per completed order (stop), regardless of order size or distance traveled. Penalizes stop count independent of everything else. |
costPerPickup | >= 0 | Flat per-stop cost | Same as costPerOrder, but applies only to pickup stops. Lets you weight pickups differently than dropoffs. |
costPerDropoff | >= 0 | Flat per-stop cost | Same as costPerOrder, but applies only to dropoff stops. |
costPerUnitTime | >= 0 | Base rate | Cost per unit of time spent traveling (not idle/service time — travel only). Defaults to 1 if unset, meaning total travel time is minimized by default. |
costPerUnitDistance | >= 0 | Base rate | Cost per unit of distance traveled. Your fuel/mileage-based cost proxy. |
costPerUnitLateTime | >= 0 | Penalty rate | Extra cost per unit of time an order is delivered past its time window. Charged in addition to the normal time cost, not instead of it. |
costPerOverorder | >= 0 | Overage rate | Cost per order once a route's stop count exceeds overorderStart. Only applies to orders past the threshold, not the whole route. |
overorderStart | > 0 | Threshold | The number of orders on a route after which the higher costPerOverorder rate kicks in instead of costPerOrder. |
costPerUnitOvertime | >= 0 | Overage rate | Cost per unit of time once a vehicle's total time exceeds overtimeStart. Applies only to time past the threshold. |
overtimeStart | > 0 | Threshold | The amount of time (per vehicle, per shift) after which "overtime" begins and costPerUnitOvertime replaces costPerUnitTime for the excess. |
costPerUnitOverdistance | >= 0 | Overage rate | Cost per unit of distance once a vehicle's total distance exceeds overdistanceStart. Applies only to distance past the threshold. |
overdistanceStart | > 0 | Threshold | The distance (per vehicle, per route) after which "overdistance" begins and costPerUnitOverdistance replaces costPerUnitDistance for the excess. |
overAdjacentDistanceCost | >= 0 | Penalty (per hop) | Flat penalty applied to a single hop between two consecutive stops when that hop's distance exceeds overAdjacentDistanceStart. Not cumulative across the route — evaluated per adjacent pair. |
overAdjacentDistanceStart | > 0 | Threshold | The distance between two adjacent stops beyond which overAdjacentDistanceCost is applied to that specific hop. Useful for discouraging long jumps between clusters. |
fixedCost | >= 0 | Per-vehicle flat cost | One-time cost charged for dispatching a vehicle at all — once per vehicle used, independent of stops, time, or distance. Higher values push the solver toward using fewer vehicles/routes. |
Reading the two rule types:
>= 0(rates and flat costs): these are literal dollar/cost-unit amounts —0is valid and just means "this cost doesn't apply."> 0(the*Startthresholds): these mark where an overage tier begins on the time/distance/order-count axis, not a cost amount —0isn't a meaningful threshold, so it's disallowed. If you don't want an overage tier at all, omit the key rather than setting it to0.
Unacceptable ValuesA request is rejected with a 400 response if
costParamsis:
- not a plain object
- an empty object
- an object containing an unsupported key
- an object containing a value that is not a finite number, or that violates the sign rule above (e.g. a negative
costPerUnitDistance, or anovertimeStartof0)
Example Request
curl -X POST "https://onfleet.com/api/v2/optimizations/scheduled" \
-u "<your_onfleet_api_key>:" \
-d '{"tasks": [
"atnk3APxhJrgfx9mgWaRhByQ",
"4kA7bOH*Kz7K0YA6~NpqWDke"
],
"teams": {
"5y1jHFp7OBfFU6SekbwwwRfL": [
"GLlLB2123ABCkXOJAHPklMPZ"
]
},
"date": 1781000000000,
"timezone": "America/Los_Angeles",
"schedulingMethod": 1,
"serviceTime": 120,
"maxViolationTime": 10,
"defaultSchedule": [
{
"date": 1780981200000,
"slots": [{"start": "0000", "end": "2359"}],
"owner": "GLlLB2123ABCkXOJAHPklMPZ",
"timezone": "America/Los_Angeles"
}
],
"costParams": {
"costPerUnitDistance": 2,
"costPerUnitTime": 1,
"costPerOrder": 0,
"overtimeStart": 3600
}}'Do not also send mode on this request — costParams replaces it entirely.
Organization Cost Model Default
An organization can set a default costParams on its route optimization profile so that it applies automatically to every scheduled optimization request that doesn't specify one directly. This setting requires Route Optimization v3 (ROv3) and is not exposed anywhere in the Onfleet dashboard — there is currently no UI to view, set, or confirm it. To enable a default clustering level for your org, contact [email protected] or your Customer Success Manager; they will configure it on the backend route optimization profile for your organization. Because there's no UI indicator, the only way to know what default (if any) is currently set for your org is to ask your CSM or Onfleet support directly.
Profiles Cannot Be MixedA route optimization profile is configured as either a
modeprofile or acostParamsprofile — never both. Sending a request whose type doesn't match the org's configured profile returns a 400 response:
- a
mode-profile org sending a request withcostParams→ 400- a
costParams-profile org sending a request withmode→ 400Pick a profile type compatible with the requests your integration sends.
Scope and Precedence
The per-request costParams (the field sent on the POST /optimizations/scheduled call) is the topmost — it takes precedence over everything else.
The order works like this, highest priority first:
- Request-level
costParams(sent in the JSON body of that specific call) — wins if present, no matter what's set at the org level. - Org-level default (the
costParamsprofile configured for your organization) — only applies if the request omitscostParamsentirely. - Preset
mode— used only if neither a request-level nor an org-levelcostParamsis in play, per the org'smodeprofile or the request'smodefield.
Organization profile setting is a fallback, not an override — it only kicks in when a given request doesn't specify its own costParams.
