Skip to main content
This guide covers the three things that change once a plan or an integration outgrows a small smoke test: how to submit many orders at once, how to page through large result sets, and how to size maxOptimizationDuration for a bigger problem.

Batch order import

There’s no separate “batch” endpoint — a single plan already carries its full orders 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 as GET /plans are always paginated. Pass page and/or itemsPerPage as query parameters to control the paging:
  • itemsPerPage — records per page (default 20, maximum 100).
  • page — 1-indexed page number (default 1).
Omitting either parameter falls back to its default rather than disabling paging — omitting both returns the first page, with the default number of items. The response wraps the collection in a 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:
  1. Start with the polling pattern (Kardinal’s recommended integration pattern) with a generous maxOptimizationDuration ceiling (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.
  2. Watch how long it actually takes to converge (successive polls stop showing objective improvements) for your real problem size and configuration.
  3. 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 maxOptimizationDuration instead of re-discovering it every time.
  4. 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 withTraffic doesn’t change the search time, only the time spent fetching travel-time matrices beforehand, which isn’t counted in maxOptimizationDuration: allow for it in how long you wait before fetching a solution.
This converges faster than picking an arbitrary starting value.
The following durations are indicative, not a guarantee. They’re the time the engine typically needs to reach a good solution on plans of that size, and a given plan can converge faster or slower depending on its constraints. Check them against your own data, with the preceding loop, for each context.
As a starting point to calibrate from — not a substitute for the preceding empirical loop: 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