> ## Documentation Index
> Fetch the complete documentation index at: https://developers.kardinal.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Data model

> Map of the objects in a plan, such as Resource, Order, Stop, Capacities, Time windows, Breaks, and Constraints, with a link to each one's full field reference.

This page is a map, not a field reference: each section below is a short orientation to one part of the plan, linking straight to the schema panel of the [API reference](/api-reference/plan/create-a-plan) that actually documents every field, type, and constraint — that's where to look up a specific field's full definition. A plan is built from two lists — `resources` (vehicles/drivers) and `orders` (what needs to be done) — plus plan-level fields that add constraints spanning several of them. See [First API call](/getting-started/first-api-call) for the request/response shapes and [How the optimization engine works](/concepts/how-the-optimization-engine-works) for what the engine does with these fields.

## Resource (vehicle-driver pair)

One vehicle-driver pair for the duration of the plan — id, working hours, capacities, skills, breaks, and travel constraints. See the [`resources` schema](/api-reference/plan/create-a-plan#body-resources).

### Vehicle profiles

Mode of transport and its travel constraints (`fly`, `pedestrian`, `bicycle`, `scooter`, `motorbike`, `car`, `truck`), nested under each resource's `vehicleProfile`. The `fly` profile additionally takes a `kmph` field — a constant travel speed used to compute crow-fly travel times, since it has no road network to derive one from. On a `truck` profile, `height`, `width`, and `length` are whole numbers of centimetres (a truck 3.8 m tall is `height: 380`), and `grossWeight` is in kilograms. A whole number in metres, such as `length: 12`, is accepted and read as 12 centimetres, with no error; round after converting, because `2.55 * 100` is not exactly `255` in most languages. See the [`resources` schema](/api-reference/plan/create-a-plan#body-resources).

## Order, stop

An order is one or more stops that must all be planned onto the same resource, in array order. See the [`orders` schema](/api-reference/plan/create-a-plan#body-orders).

A stop's `kind` (`pickup`, `delivery`, or `acknowledgement`) sets which way its `capacities` move, from the resource's point of view: a `pickup` loads its quantity at the stop, and a `delivery` unloads it there. A negative quantity reverses that direction: a `delivery` of `-100` has the same effect as a `pickup` of `100`.

Within one order, a `pickup` and a `delivery` carry the same goods: the resource loads them at the first stop and unloads them at the second. A `delivery` whose order has no `pickup` is assumed loaded at departure, which shows in the tour's `filledCapacitiesAtBegin`. Pickups and deliveries from different orders are distinct goods, even when their quantities match.

## Capacities

Free-form map tracking what a resource carries and what a stop consumes or releases, declared on both resources and stops. See the [`resources`](/api-reference/plan/create-a-plan#body-resources) and [`orders`](/api-reference/plan/create-a-plan#body-orders) schemas.

A solution reports `filledCapacitiesAtBegin`/`filledCapacitiesAtEnd`/`filledCapacitiesAfterStop` (capacities loaded at the start, end, and after each stop) and `maxFilledCapacities` (the peak reached during the tour) — see the `Tour` and `WayPointStop` schemas in the [API reference](/api-reference/solution/retrieve-a-plan-solution). A capacity declared on a resource with a ceiling no tour can reach reads back as a read-only KPI (for example `nbPackages`) without affecting the optimization.

### Shared capacity pools

Plan-level `sharedCapacities` flag pooling a capacity key across all of a resource's stops, regardless of order. See the [`sharedCapacities` schema](/api-reference/plan/create-a-plan#body-shared-capacities).

## Time windows

`TimeWindow` (plain `{begin, end}`) and `TaggedTimeWindow` (the same shape plus optional `resourceTags`), used for working hours, breaks, and a stop's authorized/preferred windows. See the [`resources`](/api-reference/plan/create-a-plan#body-resources) and [`orders`](/api-reference/plan/create-a-plan#body-orders) schemas.

## Dates and time zones

Every datetime you send lives inside the plan object, and the API accepts three ways to write one.

| Format | Example | How the API reads it |
| - | - | - |
| UTC, with a trailing `Z` | `2026-10-20T10:00:00Z` | As UTC. Milliseconds (`10:00:00.000Z`) are accepted and dropped when the plan is stored. |
| With a UTC offset | `2026-10-20T10:00:00+02:00` | At that offset. |
| Local datetime with a space instead of `T` | `2026-10-20 10:00` or `2026-10-20 10:00:00` | In the plan's `tz` time zone. Seconds are optional. |

A `Z` or an offset always wins over `tz`. For example, with `"tz": "Europe/Paris"`, the value `2026-10-20T10:00:00-05:00` is 17:00 in Paris.

Use one form for every datetime of a plan. The API accepts a plan that mixes local datetimes with `Z` or offset datetimes, and returns no error, but it reads each value in its own way, so a window can shrink without warning. For example, with `"tz": "Asia/Tokyo"`, a `workingTimeWindow` from `2026-10-20T07:00:00+02:00` to `2026-10-20 17:00` runs from 14:00 to 17:00 in Tokyo: three hours instead of ten.

A local datetime needs the space between date and time. The `T` form with no `Z` and no offset (`2026-10-20T10:00:00`) is rejected with an `INVALID_INPUT` error, even when `tz` is set. This is the form that Python's `datetime.isoformat()` writes for a datetime without a time zone, so add the correct UTC offset before you send it, or convert the value to UTC first and then add the `Z`. Adding a `Z` to a local time without converting it moves every hour.

A local datetime also needs `tz`. If the plan has no `tz`, the API does not read the value as UTC. It rejects the whole plan with an HTTP 400 `INVALID_INPUT` error, and the message does not mention `tz`. The `tz` value must be an IANA identifier such as `Europe/Paris`. A value such as `Paris` is rejected as well.

The offset depends on the date, so the same local time does not always mean the same instant. For `Europe/Paris`, `2026-10-20 10:00` is stored as `2026-10-20T10:00:00+02:00`, and `2026-11-05 10:00` is stored as `2026-11-05T10:00:00+01:00`. On the night the clocks go back, one local hour happens twice. For datetimes close to a clock change, send an explicit offset so there is no doubt.

When the plan has a `tz`, the API writes every datetime back at the offset of that zone, even if you sent it as `Z` or at another offset. The stored plan and the solution both use it. Without `tz`, the stored plan keeps each datetime in the form you sent (apart from the milliseconds), and the solution is written in `Z`. Read-only fields such as `createdAt`, `updatedAt` and the solution `timestamp` are always in `Z`. For this reason, set `tz` in every plan, even when all your datetimes carry an offset.

The API never scans `properties` sub-objects for local datetimes, so a local datetime written there is not converted. See the [`Plan` schema](/api-reference/plan/create-a-plan) for the `tz` field.

## Breaks

A resource's `breaks` array, mixing `timeWindowBreak`, `workingDurationSlidingBreak`, and `travelDurationSlidingBreak` entries. See the [`resources` schema](/api-reference/plan/create-a-plan#body-resources).

Currently, the engine schedules a break only where the vehicle already stands, at the start of its tour or at a stop, never in the middle of a drive. A single drive longer than the allowed gap between two breaks makes that drive impossible for the vehicle. The engine leaves the stops that need it unplaced, returns no error, and no violation names the break. If every tour of the vehicle needs that drive, the vehicle is not used at all. See [Where a break can fall](/guides/advanced-constraints#where-a-break-can-fall).

## Constraints catalog

`additionalConstraints` (scoped to specific tags) and `globalConstraints` (scoped to the whole fleet) beyond what resources/orders/stops express directly. See the [`additionalConstraints`](/api-reference/plan/create-a-plan#body-additional-constraints) and [`globalConstraints`](/api-reference/plan/create-a-plan#body-global-constraints) schemas.

## Plan-level fields

Fields that configure the plan as a whole: `agencyId`, [`tz`](#dates-and-time-zones), `lateDeparture`, `maxOptimizationDuration`, `sharedCapacities`, `objectives`, `additionalConstraints`, `globalConstraints`, `accessDurationsByStopTag`, `overlappingCapacitiesByStopTag`, `setupDurations`, `additionalOperationDurations`. See the [`Plan` schema](/api-reference/plan/create-a-plan).

## Status and state

Two read-only fields track a plan's lifecycle: `status` (a detailed breakdown per stage — waiting room, creation, optimization, and traffic fetching) and `state` (a single summary value, `optimized` once no better solution can be found). See [How the optimization engine works](/concepts/how-the-optimization-engine-works#the-quality-vs-computation-time-trade-off) for the full polling pattern, and `GET /plans/{planId}/state` to retrieve `state` directly.

## Positions and geocoding

Every position in a plan (each stop's `position`, and a resource's `departure`/`arrival`) is a `{lat, lon}` pair in decimal degrees. The API does not geocode addresses: geocode them on your side before building the plan, with whichever geocoding provider your integration already uses. If you can't geocode your addresses yourself, contact [customer.success@kardinal.ai](mailto:customer.success@kardinal.ai): the team can geocode an address set for you on request.

A wrongly geocoded position raises no error: it silently routes to the wrong place. Spot-check a sample of geocoded stops on a map before submitting. If an address can't be geocoded precisely, treat it as a data-quality gap rather than falling back to an approximation such as a town or postal-code centroid, since stops sharing that point collapse onto it. See [§5 of the AI agent checklist](/getting-started/agent-modeling-checklist#5-verify-you-have-real-geocoded-positions-before-finalizing), and [Geocoding and addresses](/guides/requirements-briefing#8-geocoding-and-addresses) for the questions to settle with the operations team.

## Depot vs. position

Depots are not first-class objects in this API — there is no `Depot` schema. A resource's `departure`/`arrival` positions (a raw `{lat, lon}`, or `"atFirstPosition"` for `arrival`) are how a depot is represented: resolve it to its position on the client side before building the plan. A "depot" tag such as `"depot:main"` (see [Multi-trip tours](/guides/multi-trip-tours#the-core-mechanic-pair-each-delivery-with-its-own-depot-pickup)) is an arbitrary stop tag your integration defines, not a reference to any API object. See the [`resources` schema](/api-reference/plan/create-a-plan#body-resources).

## See also

* [Hard vs soft constraints](/concepts/hard-vs-soft-constraints) — which of the preceding bend under pressure and which don't.
* [Modeling advanced constraints](/guides/advanced-constraints) — worked examples for capacities, skills, breaks, time windows, and optional steps that gate a mandatory stop.
* [How the optimization engine works](/concepts/how-the-optimization-engine-works) — objectives and the optimization loop that consumes this data.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.