Optimization objectives

Every solve optimizes toward configuration.objective, the primary objective that defines what "best" means, plus an optional secondaryObjectives[] list that refines the result without changing what "best" means. This API supports exactly one primary objective and two independent secondary objectives, a much narrower surface than the target API's objective model, so this page is the one to check before you build any objective-picking UI.

When to use this

  • You want to know what configuration.objective actually controls, and what happens if you set anything other than the one supported value.
  • A plan came back differently than you expected and you want to know whether the objective (not a constraint) is the cause.
  • You want CenterLoadMass or consolidation to refine the plan and want to know how they combine and what they cost you in the request.

The primary objective

configuration.objective supports exactly one value: "MinimizeCarriers". It fixes the cargo (every item must ship) and optimizes the number of carriers used: the solver looks for the fewest carriers, drawn from the types and quantities you offered, that can hold everything. Leftovers in unplacedItems mean the cargo genuinely doesn't fit even using every carrier you made available. The solver never drops cargo to save a trip.

Omitting the field, or sending null or "", all mean the same thing: MinimizeCarriers. Any other value is rejected with a 400, including "MaximizeFill", a primary objective in the target API that /v2 does not implement.

What quantity means

carriers[].quantity is the maximum number of that type available: never a fill target, and never a count the solver is asked to reach. MinimizeCarriers uses however few carriers, out of what you offered, are enough to ship all the cargo; a quantity: 3 entry can end up contributing anywhere from zero to three instances to the plan.

Every carrier in the plan counts. Carriers never nest, so there are no intermediate carriers to exclude from the count: each entry in the result's carriers[] is one carrier the plan uses.

Secondary objectives

An optional secondaryObjectives[] list adds either or both of two independent behaviors on top of the primary:

Type What it does
CenterLoadMass Centres the load's mass in a pass run after the solve.
consolidation Chooses the axis the solver packs along, via a signed direction: -X, -Y or Z.
{
  "configuration": {
    "secondaryObjectives": [
      { "type": "CenterLoadMass" },
      { "type": "consolidation", "direction": "-Y" }
    ]
  }
}

At most one entry per type is accepted: a second consolidation entry, or a duplicate CenterLoadMass, is a 400, not last-one-wins. A direction is required on consolidation and limited to -X, -Y or Z: +X and +Y are rejected with a 400. A direction sent alongside CenterLoadMass is accepted and ignored.

Three consequences follow directly from how these behave, and are worth being explicit about because they read differently from how the target API describes this field:

  1. Order does not matter. CenterLoadMass and consolidation are two independent, deterministic switches, not an ordered list of tie-breakers that gets applied step by step. Don't build a reorderable list for this field; a pair of independent toggles is the right UI.
  2. tolerance is rejected. Sending configuration.tolerance at all (including 0) returns a 400. It is not part of the published contract, which documents only the fields a client should send; it is called out here because a client that sends it gets a hard failure rather than having the value silently ignored.
  3. Consolidation can't be switched off, only redirected. The solver always packs along an axis; the default is -X, exactly as if you had sent { "type": "consolidation", "direction": "-X" }. Leaving consolidation out of the list is not "no consolidation"; it's consolidation toward -X by default. Present this to users as a choice among three axes, not as an on/off toggle.

Where the load ends up

What the direction changes is which face the load is held against. All three gather it toward the carrier's origin corner (x = 0, y = 0, z = 0) and fill outward from there, which is why there is no +X or +Y: the solver cannot gather a load toward the opposite end.

Three cutaway carriers, each holding the same load against a different face

The same 18 items in the same carrier under each of the three directions: -X holds them against the x = 0 end, -Y against the y = 0 side, Z on the floor. Whatever the load does not need stays clear.

Hard limits always win

With one primary objective and two secondary switches, there isn't much left for a hard limit to override, but two still bind no matter what configuration says:

  • Carrier dimensions. Cargo that doesn't physically fit inside a carrier's geometry can't be placed there; it goes to unplacedItems instead of being forced in.
  • The plan's limits. Exceeding the account's item/carrier/carrier-instance ceilings returns 422; hitting the concurrent-job or monthly-quota ceiling returns 429. Both apply to every job regardless of the objective or secondary objectives you set.

There is no per-carrier weight limit: nothing in this API enforces a maximum load weight against a carrier's capacity, so CenterLoadMass centres the mass that is loaded, but nothing caps how much of it any one carrier ends up carrying.

Key fields

Field Where Purpose
configuration.objective request The only supported primary: MinimizeCarriers. Also the default when omitted.
configuration.secondaryObjectives request Independent switches, at most one per type: CenterLoadMass, consolidation (with a direction).
configuration.tolerance request Not supported: supplying any value returns a 400.
  • Constraints: the hard stacking/separation rules objectives never override.