maxOptimizationDuration for a bigger problem.
Batch order import
There’s no separate “batch” endpoint — a single plan already carries its fullorders array, so importing in bulk means submitting all of it in one request rather than one order at a time: POST /plans for the initial import, or PUT /plans/{planId} for a full replacement of an existing plan. The request body is always inline JSON with the complete resources/orders arrays, as shown in First API call: the API doesn’t accept file uploads or XLSX.
For a large order set sourced from a spreadsheet or a WMS/TMS export, convert it to the JSON plan format on your side before submitting, and submit the whole plan in one call. You can add orders to an existing plan later, but each PUT /plans/{planId} must carry the complete plan: every resource and order already in it, plus the new ones. Anything left out of the body is removed from the plan. Every update also re-triggers optimization (see How the optimization engine works), so many small updates are slower than one large submission for an initial import.
The maximum payload size is 3,000 orders, 3,000 stops, and 250 resources per plan by default — see Limits and quotas for the full defaults. If you’re importing an unusually large order set, confirm the ceiling with customer.success@kardinal.ai before building an automated pipeline around a single large request.
Paginating results
List endpoints such asGET /plans are always paginated. Pass page and/or itemsPerPage as query parameters to control the paging:
itemsPerPage— records per page (default20, maximum100).page— 1-indexed page number (default1).
paging object (page, nextPage, previousPage, itemsPerPage) so you can walk forward without recomputing offsets yourself.
Sizing maxOptimizationDuration for a large problem
No table can map problem size to an exact optimization duration — how long a plan needs depends on more than just stop and resource counts (see the full list of drivers in How the optimization engine works): whether advanced constraints (AlternativesStop, removalStrategy: "lifo", overlappingCapacitiesByStopTag) are in play — any of these can switch the engine to a markedly slower algorithm regardless of raw problem size.
In practice, size maxOptimizationDuration empirically rather than guessing a fixed value up front:
- Start with the polling pattern (Kardinal’s recommended integration pattern) with a generous
maxOptimizationDurationceiling (for example,PT1H) — the engine stops early on its own once it stops finding improvements, so an overly long ceiling costs you nothing but a slightly longer worst case. - Watch how long it actually takes to converge (successive polls stop showing objective improvements) for your real problem size and configuration.
- For recurring plans of similar shape (same rough stop/resource count, same constraint set), use that observed convergence time, with margin, as your steady-state
maxOptimizationDurationinstead of re-discovering it every time. - Re-run this calibration whenever problem size changes by an order of magnitude, or when you turn on an advanced constraint for the first time — it’s known to change convergence time independently of stop/resource count. Turning on
withTrafficdoesn’t change the search time, only the time spent fetching travel-time matrices beforehand, which isn’t counted inmaxOptimizationDuration: allow for it in how long you wait before fetching a solution.
For a plan between two rows, start from the larger one. Past about 800 stops, set
maxOptimizationDuration explicitly: if it’s omitted, production plans stop after 15 minutes, which is less than the time the largest plans need (see Limits and quotas).
See also
- How the optimization engine works — objectives, the
maxOptimizationDurationtrade-off, and the three integration patterns (static, iterative, polling). - Limits and quotas — payload size, rate limits, and the waiting-room throughput mechanism.

