Skip to main content
A plan goes through the same steps whatever the SDK: you submit it, the engine optimizes it, you read the solution, and you update the plan as reality changes. This page follows those steps. See How the optimization engine works for what happens on the engine’s side.

Submitting a plan

submit creates the plan and starts its optimization in the same call. It returns the stored plan, with the id the server assigned and the warnings about anything the API amended. Submitting consumes credits with a production key: see Pricing and credits. submit is never retried automatically, even after a network error: the API has no idempotency key, so a retry could create the plan twice. If a submit fails without a response, check with plans.list whether the plan exists before submitting again.

Waiting for the solution

wait follows the plan until the optimization of its current version is over, then returns the solution:
  • It reads the plan’s state every 10 seconds at first, stretching to every 60 seconds on long optimizations, and fetches the solution only once the plan is done. It doesn’t sleep through the whole maxOptimizationDuration: the engine often stops earlier, once it stops finding improvements.
  • The solution’s final state says how the optimization ended: optimized, or interrupted or stopped when it was cut short. Check it before treating the result as final.
  • It fails with KardinalOptimizationError when there’s no solution to return: NO_SOLUTION (the version ended without one), PLAN_SUPERSEDED (a newer version was submitted meanwhile), or OPTIMIZATION_STALLED (still optimizing 5 minutes past its maxOptimizationDuration). A plan deleted while waiting fails with KardinalNotFoundError.
  • The optional time limit (timeout in seconds in Python, timeoutMs in milliseconds in TypeScript) fails with KardinalTimeoutError once elapsed, after checking the state one last time.

Tracking progress without blocking

To follow a plan without waiting, read its state and fetch the solution yourself:
solutions.get returns the current best solution right away, even while the engine is still improving it. Right after submit, it fails with a 404 until the engine has a first solution. Like any manual tracking, read a plan’s state no more than once every 10 seconds.

Waiting in a web backend or serverless function

wait is meant for scripts, notebooks, and workers. In a request handler, don’t hold the request open while the engine runs:
  • Submit the plan, store its id, and return right away.
  • Read the state later — from a scheduled job, a queue worker, or the next request from your front end — and fetch the solution once the plan is done.
  • A serverless function usually has a shorter time limit than an optimization: track the plan across invocations instead of waiting in one.
The client is synchronous. In an asyncio app, run blocking calls in a thread (await asyncio.to_thread(k.routing.solutions.wait, plan_id)) so they don’t block the event loop, or track the plan with get_status, as shown earlier.

Re-optimizing with update

When something changes — a new order, a cancelled one, a vehicle out of service — update the plan rather than submitting a new one. The engine continues from the current solution instead of starting over: see Real-time re-optimization.
  • update replaces the whole plan: anything left out is removed. Start from the plan returned by get, submit, or a previous update, and modify it.
  • The version increments, and the plan is re-optimized, only if the content actually changed. An identical update is a free no-op that keeps the same version, which makes replaying an update harmless.
  • A field the API added after your SDK version, read by get, is kept and sent back, so updating with an older SDK doesn’t delete it.
  • An archived plan refuses an update without force, with a 403 whose details is Cannot update an archived plan, except with the 'force' query param. force=True (Python) or { force: true } (TypeScript) unarchives it first. On an archived plan, a forced update always creates a new version and re-optimizes the plan, even if its content is identical, so every stop is billed again, as for any update or restart made more than 24 hours after the plan’s creation. On any other plan, force has no effect. Don’t set it by default.

Starting and stopping optimization

Both keep the plan’s content and version unchanged, and return a reference to the plan (agencyId, planId, planVersion). A restart with start is billed like an update, by the 24-hour rule of Pricing and credits, and it counts as one optimization against your agency’s daily and monthly optimization quota (see Limits and quotas). An archived plan refuses start and stop without force, with the same 403 as for update. force unarchives the plan first, for a stop too. An archived plan is more than 24 hours old, so a forced restart bills every stop again.

Listing plans

Plans come in their light form, without resources and orders: call get for the full plan. Archived plans are left out by default; pass archived="included" or "only" (Python) or { archived: "included" } (TypeScript) to change that. A page past the last one returns the last page: its page field gives the page actually returned. See Paginating results.

Deleting a plan

Deleting a plan removes it and its solution permanently, and stops any optimization in progress. It can’t be undone.