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 PUT request rather than one order at a time. As shown in First API call, you can submit either:
  • an inline JSON body (-d) with the complete resources/orders arrays, or
  • a file upload (-F "file=@plan.json"), as JSON or as XLSX — column names in the spreadsheet match the JSON field names.
For a large order set sourced from a spreadsheet or a WMS/TMS export, the file-upload path avoids building and escaping one large JSON string by hand. Whichever path you use, submit the whole plan in one call — updating a plan by adding orders incrementally (many small PUTs to the same plan id) is supported (see How the optimization engine works) but re-triggers optimization on every update, which is slower than one large submission for an initial import.
The maximum payload size accepted per request is not yet published in this documentation — see Limits and quotas. If you’re importing an unusually large order set, confirm the ceiling with support@kardinal.ai before building an automated pipeline around a single large request.

Paginating results

List endpoints such as GET /plans are unpaginated by default — every matching record is returned in one response. Pass page and/or itemsPerPage as query parameters to switch to paging:
  • itemsPerPage — records per page (default 20, maximum 100).
  • page — 1-indexed page number (default 1).
If you set only one of the two, the other falls back to its default rather than disabling paging — paging only stays off if both are omitted. 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

There’s no published lookup table mapping 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 resources use withTraffic: true, and 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 (e.g. 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 withTraffic or an advanced constraint for the first time — both are known to change convergence time independently of stop/resource count.
This converges faster than picking an arbitrary starting value.
A common mistake is assuming small-plan durations like PT10M scale up to fleets of 10+ vehicles and 100+ orders — they don’t. A much longer budget, on the order of hours rather than minutes (e.g. PT4H), is often needed to reach a comparable solution quality at that size.
As a starting point to calibrate from — not a substitute for the empirical loop above:

See also