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.
  • On an archived plan, force=True (Python) or { force: true } (TypeScript) unarchives it first; on any other plan, force has no effect.

Starting and stopping optimization

Both keep the plan’s content and version unchanged, and return a reference to the plan (agencyId, planId, planVersion). force unarchives an archived plan first, as for update.

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.