Skip to main content
Both SDKs map every API error to the same exception classes, with the same names in Python and TypeScript. All of them inherit from KardinalError and can be imported from the package root.

Error families

A code the SDK doesn’t know yet falls back to the family matching its HTTP status, so a new code added by the API doesn’t go unnoticed. For the meaning of each code, see the Error schema in the API Reference.

Common error attributes

Every exception carries the same attributes, named in each language’s case: retryable is true for a 429, and for a 5xx or a connection error — except when a submit may have reached the server, since the plan may then already exist.

Validation errors

A 400 means the API refused the plan as sent. errors holds every problem found, each with its code and, in properties.path, the field concerned: fix them all before submitting again rather than one per attempt. Most structural mistakes never reach the API: the SDKs catch them locally first (see Validating locally before you spend credits).

Quotas and rate limits

403: a limit was hit. For a builder account, a 403 always means a limit, never a permission refusal: the SDKs raise KardinalLimitError for every one of them, and details says which.
  • Plan-size limit (too many resources, orders, or stops, or more than 4 stops in an order without successiveStops): errors also holds one INVALID_VALUE entry per exceeded limit, naming the field and the allowed value. Shrink the plan.
  • Daily or monthly optimization quota: wait, or ask for a higher quota.
  • Insufficient credits: top up your credits in the Console.
See Reaching a limit (403) and Pricing and credits for the values. 429: a temporary overload, not a quota. It comes with an empty body. The SDKs retry it on their own, honouring a Retry-After header if the API sends one, and backing off otherwise. See Temporary overload (429).

What the SDKs retry

The number of retries is set by max_retries / maxRetries (3 by default), with a growing, randomized delay between attempts. submit is never retried because the API has no idempotency key: a retried submit could create the plan twice. After a failed submit, check with plans.list whether the plan exists before submitting again.

Client-side errors

Some errors are raised before any request is sent:
  • UnknownFieldError — submit refuses a plan containing a key no model declares, and lists each one with its path and the closest real field name. The API would drop such a key without an error: a misspelled operationDurationn would leave the stop with no service time, and the plan would still look valid.
    update sorts unknown keys by origin: a key the server sent, from a field added after your SDK version, is sent back untouched; a key you added is refused as a typo. To send a field the API supports but your SDK version doesn’t know yet, name it explicitly: allow_unknown=["aNewField"] (Python) or { allowUnknown: ["aNewField"] } (TypeScript). A name listed there is allowed anywhere in the plan, so prefer distinctive names.
  • Model validation — a missing required field or a wrong type fails when the plan is built or parsed: pydantic.ValidationError in Python, ZodError in TypeScript.
  • Missing key — MissingApiKeyError and EmptyApiKeyError, raised when creating the client — see API key resolution.
On a plan this SDK didn’t read from the API (built by hand, or loaded from a file), update can’t tell where an unknown key came from: it raises an UnknownFieldWarning through Python’s warnings module, only for a key within two edits of a real field, and sends the plan.

Warnings on successful calls

When the API accepts a plan but amends part of it — a maxOptimizationDuration clamped to your agency’s cap, crow-fly distances forced in the sandbox, predictive traffic turned off — submit and update return the amendments in warnings:
Read them from that return value: warnings aren’t stored with the plan, so a later get never returns them, only the amended values. Solutions, objective lists, and the references returned by start and stop carry a warnings field too. The Warning schema in the API Reference lists what each one means.