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

# Validate solve

> Check a solve request without running it

> [!NOTE]
>
> **V3 is in beta.** General availability (GA) is planned for January 2027.


## OpenAPI

````yaml vrp-v3 POST /v3/routing/validate/solve
openapi: 3.1.0
info:
  title: Solvice Routing Solver
  description: >-
    Optimizes vehicle routes to minimize distance, time, or fleet size while
    respecting capacity, time windows, skills, and custom constraints.


    **Authentication:** send your Solvice API key as the value of the
    `Authorization` header, with no `Bearer` prefix, to
    `https://api.solvice.io`.
  contact:
    name: Solvice
    url: https://www.solvice.io
  license:
    name: Proprietary
  version: 3.0.0
servers:
  - url: https://api.solvice.io
    description: Production — Solvice API gateway
security:
  - apiKey: []
tags:
  - name: Routing
    description: Vehicle routing optimization
paths:
  /v3/routing/validate/solve:
    post:
      tags:
        - Routing
      summary: Check a solve request without running it
      description: >-
        Checks a `POST /v3/routing/solve` request without solving it. It runs
        the checks `POST /v3/routing/solve` runs before it fetches travel times,
        and stops there: no travel times are fetched, nothing is searched, no
        solver slot is taken, and the call is not billed. The body is the body
        of `POST /v3/routing/solve`. A valid request returns `{"valid": true,
        "warnings": []}`; an invalid one returns the 400 (or 403) the real call
        would. Not covered, because the real call makes these checks only after
        it fetches travel times: coordinates that span more than one Maps region
        (a 400 on the real call), and the warm-start `plan` (that its routes and
        stops name vehicles, shifts, tasks and depots of `problem`, and that it
        imports, its locked stops included; see the plan errors in the errors
        guide).
      operationId: validate_solve
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SolveRequest'
        required: true
      responses:
        '200':
          description: >-
            The request passed every check made before travel times are fetched.
            `warnings` may contain warnings; none are produced today.
          headers:
            x-request-id:
              schema:
                type: string
              description: The request's `X-Request-Id`, echoed, or a generated `req_…` id.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidateResponse'
        '400':
          description: >-
            The request is invalid. `errors[]` lists what to fix, each entry
            with a `code` and, when the problem is in the body, a JSON `pointer`
            to it.
          headers:
            x-request-id:
              schema:
                type: string
              description: The request's `X-Request-Id`, echoed, or a generated `req_…` id.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorBody'
        '401':
          description: >-
            Missing or invalid API key. Returned by the API gateway before the
            request reaches the solver, so it does not use the `ErrorBody` shape
            and can have an empty body. Branch on the HTTP status.
        '403':
          description: >-
            The request uses a feature this account does not have
            (`tomtom_real_time`)
          headers:
            x-request-id:
              schema:
                type: string
              description: The request's `X-Request-Id`, echoed, or a generated `req_…` id.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorBody'
              example:
                code: feature_not_enabled
                message: >-
                  travel.engine: 'tomtom_real_time' is not enabled for this
                  account; use engine 'tomtom'
                request_id: req_7f3a2c90d1e4b5a6
                status: FORBIDDEN
        '413':
          description: >-
            Request body larger than 10 MiB (10,485,760 bytes), counted after
            gzip decompression
          headers:
            x-request-id:
              schema:
                type: string
              description: The request's `X-Request-Id`, echoed, or a generated `req_…` id.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorBody'
              example:
                code: payload_too_large
                message: Request body exceeds 10485760 bytes
                request_id: req_7f3a2c90d1e4b5a6
                status: PAYLOAD_TOO_LARGE
        '415':
          description: >-
            Request `Content-Type` is missing or not JSON (`application/json` or
            `application/*+json`), or the body has a `Content-Encoding` other
            than `gzip` (the message says which)
          headers:
            x-request-id:
              schema:
                type: string
              description: The request's `X-Request-Id`, echoed, or a generated `req_…` id.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorBody'
              example:
                code: unsupported_media_type
                message: 'Expected request with `Content-Type: application/json`'
                request_id: req_7f3a2c90d1e4b5a6
                status: UNSUPPORTED_MEDIA_TYPE
        '429':
          description: >-
            Rate limited. Returned by the API gateway before the request reaches
            the solver, so it does not use the `ErrorBody` shape. Back off and
            retry.
        '500':
          description: 'Server-side error (`code: internal`).'
          headers:
            x-request-id:
              schema:
                type: string
              description: The request's `X-Request-Id`, echoed, or a generated `req_…` id.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorBody'
components:
  schemas:
    SolveRequest:
      type: object
      description: Request body for `POST /v3/routing/solve`.
      required:
        - problem
      properties:
        options:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/SolveOptions'
              description: Solve options.
          description: Solve options.
        plan:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/Plan'
              description: >-
                Warm start: the plan the search starts from instead of
                constructing

                from scratch (re-optimization). A result's `plan` goes in as it
                stands.

                See `plan.routes[].locked_count` for freezing a dispatched
                prefix.


                A **hint, not a guarantee**: an unlocked stop that violates a
                hard

                constraint (e.g. capacity, skills, eligibility, `limits.*`, or a
                time

                window with no `per_late_hour` set) is left unassigned and the

                search reinserts it wherever it legitimately fits, or it
                surfaces in

                `unassigned[]` with a typed reason. An infeasible locked stop is
                a 400.
          description: >-
            Warm start: the plan the search starts from instead of constructing

            from scratch (re-optimization). A result's `plan` goes in as it
            stands.

            See `plan.routes[].locked_count` for freezing a dispatched prefix.


            A **hint, not a guarantee**: an unlocked stop that violates a hard

            constraint (e.g. capacity, skills, eligibility, `limits.*`, or a
            time

            window with no `per_late_hour` set) is left unassigned and the

            search reinserts it wherever it legitimately fits, or it surfaces in

            `unassigned[]` with a typed reason. An infeasible locked stop is a
            400.
        problem:
          $ref: '#/components/schemas/Problem'
          description: 'The problem: tasks, fleet, depots, relations, objective, travel.'
      additionalProperties: false
      example:
        options:
          seed: 42
          time_limit_s: 5
        problem:
          depots:
            - id: gent
              location:
                - 3.7174
                - 51.0543
          jobs:
            - id: job-1
              location:
                - 3.725
                - 51.05
              pickup_load:
                weight: 20
              required_skills:
                - name: fridge
              service_duration_s: 300
              time_windows:
                - from: '2026-07-01T09:00:00+02:00'
                  to: '2026-07-01T12:00:00+02:00'
            - id: job-2
              location:
                - 3.71
                - 51.06
              pickup_load:
                weight: 35
              service_duration_s: 600
          objective:
            costs:
              per_route_hour: 35
              per_travel_km: 0.2
            currency: EUR
          vehicles:
            - capacity:
                weight: 500
              id: v1
              profile: car
              shifts:
                - end:
                    depot: gent
                  from: '2026-07-01T08:00:00+02:00'
                  id: mon
                  start:
                    depot: gent
                  to: '2026-07-01T17:00:00+02:00'
              skills:
                - name: fridge
    ValidateResponse:
      type: object
      description: >-
        The 200 body of `POST /v3/routing/validate/{solve,evaluate,suggest}`:
        the

        request passed every check a real call runs before the matrix fetch.
      required:
        - valid
        - warnings
      properties:
        valid:
          type: boolean
          description: 'Always `true`: an invalid request gets a 400 instead.'
        warnings:
          type: array
          items:
            $ref: '#/components/schemas/RequestIssue'
          description: May contain warnings about the request; none are produced today.
      example:
        valid: true
        warnings: []
    ErrorBody:
      type: object
      description: >-
        Error body of every `/v3/routing/*` endpoint.


        `status` is the HTTP status as text, `code` says what kind of failure it

        is, `request_id` identifies the request, and `errors` holds one item per

        problem found in the request. Branch on `code`, and on `errors[].code`
        for

        400s; the messages are for people.
      required:
        - status
        - code
        - message
      properties:
        code:
          $ref: '#/components/schemas/ErrorCode'
          description: What kind of failure this is. One value per HTTP status class.
        errors:
          type: array
          items:
            $ref: '#/components/schemas/RequestIssue'
          description: >-
            One entry per problem found in the request. Empty for non-input
            errors.
        message:
          type: string
          description: >-
            Human-readable summary. With exactly one item it is that item's

            message; with several, `"{n} problems in request"` (at most 100
            items

            are listed, and the message says when the list is cut).
        request_id:
          type:
            - string
            - 'null'
          description: >-
            The `X-Request-Id` of the request (echoed, or generated when
            absent).
        status:
          type: string
          description: |-
            The HTTP status as text, e.g. `"BAD_REQUEST"` or
            `"SERVICE_UNAVAILABLE"`.
          example: BAD_REQUEST
      example:
        code: invalid_request
        errors:
          - code: out_of_range
            message: 'depot ''gent'': service_duration_s must be >= 0, got -60'
            pointer: /problem/depots/0/service_duration_s
        message: 'depot ''gent'': service_duration_s must be >= 0, got -60'
        request_id: req_7f3a2c90d1e4b5a6
        status: BAD_REQUEST
    SolveOptions:
      type: object
      description: Solve options.
      properties:
        polyline:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/PolylineDetail'
              description: >-
                Return road geometry for each route as `routes[].polyline`:

                `simplified` or `full`. Omitted → no geometry (the default). One
                value

                for the whole request, named like the response field it fills.
          description: >-
            Return road geometry for each route as `routes[].polyline`:

            `simplified` or `full`. Omitted → no geometry (the default). One
            value

            for the whole request, named like the response field it fills.
        seed:
          type:
            - integer
            - 'null'
          format: int64
          description: |-
            Random seed for the search. Default `42`: omitting it is the same as
            sending `42`, so send different values to explore different plans.
            Repeated runs on one machine usually return an identical plan; a
            materially faster or slower machine can differ.
          default: 42
          example: 42
          minimum: 0
        time_limit_s:
          type:
            - number
            - 'null'
          format: double
          description: >-
            Search time budget in seconds, fractions allowed. Default when
            omitted:

            150 ms plus 4 ms per task (jobs and shipments combined), so 100
            tasks

            search for 0.55 s. Maximum 60: a larger value is rejected with HTTP
            400.

            A value below 0.1 is raised to a 100 ms floor. Pure search time:

            fetching travel times and waiting for capacity are not deducted from
            it,

            so the request takes longer than this value.
          example: 2.5
          maximum: 60
          minimum: 0
        travel_fallback:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/TravelFallback'
              description: >-
                What to do when travel times cannot be fetched from Solvice
                Maps:

                `estimate` (the default) solves on estimated travel and sets

                `summary.travel_estimated`; `fail` returns the 502/503 instead.

                Locations that Maps rejects for spanning more than one region
                are a 400.

                No effect with `travel.engine: "straight_line"`, which fetches
                nothing.
          description: >-
            What to do when travel times cannot be fetched from Solvice Maps:

            `estimate` (the default) solves on estimated travel and sets

            `summary.travel_estimated`; `fail` returns the 502/503 instead.

            Locations that Maps rejects for spanning more than one region are a
            400.

            No effect with `travel.engine: "straight_line"`, which fetches
            nothing.
          default: estimate
      additionalProperties: false
      example:
        polyline: full
        seed: 42
        time_limit_s: 5
    Plan:
      type: object
      description: >-
        A plan: which stops each vehicle shift serves, in order. A result's
        `plan`

        has exactly this shape and goes back into evaluate, suggest or a

        warm-started solve unchanged.
      required:
        - routes
      properties:
        routes:
          type: array
          items:
            $ref: '#/components/schemas/PlanRoute'
          description: One entry per vehicle shift that serves something.
      additionalProperties: false
    Problem:
      type: object
      description: The problem part of every V3 request body.
      required:
        - vehicles
      properties:
        depots:
          type: array
          items:
            $ref: '#/components/schemas/Depot'
          description: Depots that shifts start from, end at, or reload at, by id.
        jobs:
          type: array
          items:
            $ref: '#/components/schemas/Job'
          description: >-
            Single-stop visits to serve. At most 10,000 tasks per request,
            `jobs`

            and `shipments` combined (HTTP 400 `limit_exceeded` beyond that).
          maxItems: 10000
        objective:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/Objective'
              description: Objective configuration.
          description: Objective configuration.
        quotas:
          type: array
          items:
            $ref: '#/components/schemas/Quota'
          description: Stop caps shared by a set of vehicles. At most 65,535.
          maxItems: 65535
        relations:
          type:
            - array
            - 'null'
          items:
            $ref: '#/components/schemas/Relation'
          description: Ordering, co-assignment and synchronization constraints.
        shipments:
          type: array
          items:
            $ref: '#/components/schemas/Shipment'
          description: |-
            Bound pickup→delivery shipments to serve. Counts toward the same
            10,000-task cap as `jobs`.
          maxItems: 10000
        travel:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/Travel'
              description: Where travel times and distances come from.
          description: Where travel times and distances come from.
        vehicles:
          type: array
          items:
            $ref: '#/components/schemas/Vehicle'
          description: 'Available vehicles: 1 to 2,000 (HTTP 400 otherwise).'
          maxItems: 2000
          minItems: 1
      additionalProperties: false
      example:
        depots:
          - id: gent
            location:
              - 3.7174
              - 51.0543
        jobs:
          - id: job-1
            location:
              - 3.725
              - 51.05
            pickup_load:
              weight: 20
            service_duration_s: 300
        vehicles:
          - capacity:
              weight: 500
            id: v1
            shifts:
              - end:
                  depot: gent
                from: '2026-07-01T08:00:00+02:00'
                id: mon
                start:
                  depot: gent
                to: '2026-07-01T17:00:00+02:00'
    RequestIssue:
      type: object
      description: One problem in a request.
      required:
        - code
        - message
      properties:
        actual:
          type:
            - integer
            - 'null'
          format: int64
          description: '`limit_exceeded` only: the count this request has.'
          minimum: 0
        code:
          $ref: '#/components/schemas/ErrorItemCode'
          description: What is wrong.
        limit:
          type:
            - integer
            - 'null'
          format: int64
          description: '`limit_exceeded` only: the published cap.'
          minimum: 0
        message:
          type: string
          description: Human-readable description.
        pointer:
          type:
            - string
            - 'null'
          description: |-
            RFC 6901 JSON Pointer into the request body, when the problem has a
            location. It is relative to the root of the body, so a field of the
            problem reads `/problem/...` (e.g.
            `"/problem/vehicles/0/shifts/0/from"`).
    ErrorCode:
      type: string
      description: >-
        The top-level `code` of an error body.


        Known values: `invalid_request`, `feature_not_enabled`,
        `payload_too_large`, `unsupported_media_type`, `internal`,
        `travel_provider_error`, `overloaded`. An open set: a later release may
        add values, so accept any string and handle the ones you know.
      x-extensible-enum:
        - invalid_request
        - feature_not_enabled
        - payload_too_large
        - unsupported_media_type
        - internal
        - travel_provider_error
        - overloaded
    PolylineDetail:
      type: string
      description: >-
        How much road detail `routes[].polyline` carries.


        This is the knob that decides whether a drawn route follows the roads or

        cuts corners — **not** the polyline encoding precision, which is fixed
        at 5

        and is a sub-metre concern. Measured on a three-stop, 150 km route:


        | detail | shape points | geometry bytes | added latency |

        |--------|--------------|----------------|---------------|

        | `simplified` | 34 | ~200 | ~0 |

        | `full` | 1,728 | ~5,300 | ~0 (inside run-to-run noise) |


        Prefer `full` unless response size is the binding constraint: the extra

        detail is nearly free in time, and `simplified` is what makes a route
        look

        like straight lines between stops.
      enum:
        - simplified
        - full
      example: full
    TravelFallback:
      type: string
      description: |-
        `options.travel_fallback`: what a solve does when travel times cannot be
        fetched. `/evaluate` and `/suggest` always estimate.
      enum:
        - estimate
        - fail
      example: estimate
    PlanRoute:
      type: object
      description: >-
        One vehicle shift's route: every trip of the shift, split by `reload`
        stops.
      required:
        - vehicle
        - shift
        - stops
      properties:
        locked_count:
          type:
            - integer
            - 'null'
          format: int32
          description: >-
            On solve, the first `locked_count` stops stay where they are
            (already

            dispatched). Counts every listed stop. Cannot be combined with a

            `reload` stop, and must not exceed the route's stop count — both are

            validated on every endpoint that takes a plan. Otherwise inert on

            evaluate. On suggest, no insertion lands before them.
          example: 1
          minimum: 0
        shift:
          type: string
          description: Shift id, within that vehicle.
        stops:
          type: array
          items:
            $ref: '#/components/schemas/PlanStop'
          description: Stops in visiting order.
        vehicle:
          type: string
          description: Vehicle id.
      additionalProperties: false
      example:
        shift: mon
        stops:
          - id: boiler-17
            type: job
        vehicle: van-3
    Depot:
      type: object
      description: >-
        A depot: a place a shift starts from, ends at, or reloads at mid-shift,

        referenced by `id` from `shifts[].start`, `shifts[].end` and

        `shifts[].reloads`. Each depot keeps its own handling time and reload
        cost.
      required:
        - id
        - location
      properties:
        id:
          type: string
          description: 'Depot id: 1 to 128 characters, unique across `depots[]`.'
          example: gent
          maxLength: 128
          minLength: 1
        location:
          $ref: '#/components/schemas/Place'
          description: Where the depot is.
        per_reload:
          type:
            - number
            - 'null'
          format: double
          description: >-
            Money charged once per reload visit, in the objective's currency.

            Default 0; at most `1e9`. A shift that only starts or ends here is
            not

            charged.


            Set this when a reload has a real per-reload price the travel terms

            cannot see — typically the driver labour for the unload itself

            (`service_duration_s` × the hourly rate). Without it, a reload that

            consumes only *slack* is free in the objective: it adds no route

            duration, so the solver will happily insert one to displace time
            that

            `objective.costs.per_wait_hour` would otherwise charge as idle,
            parking

            the vehicle at a depot for no capacity benefit.
          example: 38.66
          maximum: 1000000000
          minimum: 0
        service_duration_s:
          type:
            - integer
            - 'null'
          format: int32
          description: >-
            Handling time in seconds per visit: the load before a shift that
            starts

            here, the unload after a shift that ends here, and each reload. A
            shift

            end's own `service_duration_s` overrides it for that end. Default 0.

            The unload after a shift must finish by that shift's `to` (or

            `to + max_overtime_s`). Not scaled by `service_factor`: this is

            depot-side handling, not the driver's on-site work rate.
          example: 900
          minimum: 0
      additionalProperties: false
      example:
        id: gent
        location:
          - 3.7174
          - 51.0543
        service_duration_s: 900
    Job:
      type: object
      description: |-
        A single-stop visit: one location served once (the `jobs[]` entry type).
        For a pickup bound to a delivery on the same vehicle, use `shipments[]`.
      required:
        - id
      properties:
        assignment_costs:
          type:
            - array
            - 'null'
          items:
            $ref: '#/components/schemas/AssignmentCost'
          description: >-
            Absolute price of serving this task on a given vehicle, in the

            objective's currency. Charged once, when that vehicle (on any of its

            shifts) serves the task; unlisted vehicles cost nothing. Unlike

            `preferences`, which charges the vehicles that did NOT get the task,

            this prices the vehicle that did, so a task rated badly everywhere
            is

            the first one dropped when capacity runs out. It lives in the cost
            tier,

            below `priority`, and adds to `preferences`, `drop_cost` and travel.

            Not applied by decomposition: a problem that sets it solves
            monolithically.
        break_interruptible:
          type:
            - boolean
            - 'null'
          description: >-
            Whether a break may pause this job's service. With `true`, a `fixed`

            break, a `windowed` break or a duty-`triggered` break that falls

            inside the service pauses it: the work resumes when the break ends,

            the job departs the break's length later, and the stop reports the

            paused seconds in `break_s`. A drive-`triggered` break never pauses
            a

            job (it counts driving time only). Default `false`: the whole
            service

            moves after the break, or the break is taken before the job starts.
          example: true
        delivery_load:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/Quantities'
              description: >-
                Quantity delivered at this visit: comes off the vehicle here,
                having

                been loaded at a depot. Direction is carried by the field
                itself, never

                by sign — `delivery_load` values are non-negative magnitudes,
                like `pickup_load`.


                Unlike a shipment, nothing is picked up for it at another stop:
                the

                goods are assumed already aboard from a depot, so the vehicle
                departs

                its trip carrying the sum of the trip's `delivery_load`
                quantities.


                Absent or empty means no delivery at this visit.
          description: >-
            Quantity delivered at this visit: comes off the vehicle here, having

            been loaded at a depot. Direction is carried by the field itself,
            never

            by sign — `delivery_load` values are non-negative magnitudes, like
            `pickup_load`.


            Unlike a shipment, nothing is picked up for it at another stop: the

            goods are assumed already aboard from a depot, so the vehicle
            departs

            its trip carrying the sum of the trip's `delivery_load` quantities.


            Absent or empty means no delivery at this visit.
        drop_cost:
          type:
            - number
            - 'null'
          format: double
          description: >-
            Drop **price** — what leaving this task unserved costs you, in the
            same

            currency as `objective.costs`, and the only way to make a task

            droppable. Tasks without a `drop_cost` must be served.


            This is money, not a priority: it enters the same cost the solver

            minimises, so the task is served only while serving it costs less
            than

            dropping it. Drop costs the plan chose to pay are reported in

            `score.components.drop_costs` and summed into `score.cost`.


            On a shipment the drop cost applies to the pair, charged once.


            Must be finite, `>= 0` and at most `1e9`; combined with `priority`
            it

            is rejected with HTTP 400 (`conflicting_fields`). Also rejected with

            HTTP 400 (`not_supported`) while a positive
            `objective.per_delay_workday` is set,

            since a droppable task could be dropped to avoid its delay charge.
          example: 25
          maximum: 1000000000
          minimum: 0
        eligible_vehicles:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/VehicleEligibility'
              description: >-
                Hard vehicle allow/exclude. A request may use at most 256
                distinct

                permitted-vehicle sets (after resolving `allowed`/`excluded`)
                across

                all jobs and shipments (HTTP 400 `limit_exceeded` beyond that).
          description: |-
            Hard vehicle allow/exclude. A request may use at most 256 distinct
            permitted-vehicle sets (after resolving `allowed`/`excluded`) across
            all jobs and shipments (HTTP 400 `limit_exceeded` beyond that).
        id:
          type: string
          description: 'Job id: 1 to 128 characters, unique across `jobs` + `shipments`.'
          example: job-1
          maxLength: 128
          minLength: 1
        location:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/Place'
              description: >-
                Where this visit happens. Omit for a "virtual" (location-less)
                job:

                it still has service duration, time windows, load, and skills,
                but

                requires no travel — the vehicle's position doesn't change when
                it

                serves one. Virtual and located jobs may be mixed freely in the
                same

                request and route. A virtual job's stop in the solution response

                reports the resolved location of its nearest real (located)

                neighbor in the route — the predecessor when there is one,
                otherwise

                the successor — so it is `null` only on a route that has no
                located

                stop at all (an all-virtual route on an open-start/open-end
                shift).

                A shipment's legs still require a `location`.
          description: |-
            Where this visit happens. Omit for a "virtual" (location-less) job:
            it still has service duration, time windows, load, and skills, but
            requires no travel — the vehicle's position doesn't change when it
            serves one. Virtual and located jobs may be mixed freely in the same
            request and route. A virtual job's stop in the solution response
            reports the resolved location of its nearest real (located)
            neighbor in the route — the predecessor when there is one, otherwise
            the successor — so it is `null` only on a route that has no located
            stop at all (an all-virtual route on an open-start/open-end shift).
            A shipment's legs still require a `location`.
        pickup_load:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/Quantities'
              description: >-
                Quantity picked up at this visit: goes onto the vehicle here and
                stays

                aboard until the route ends or reloads. Non-negative magnitudes
                per

                named dimension. A swap that collects one quantity and drops off

                another at the same address is one job with both `pickup_load`
                and

                `delivery_load`.
          description: >-
            Quantity picked up at this visit: goes onto the vehicle here and
            stays

            aboard until the route ends or reloads. Non-negative magnitudes per

            named dimension. A swap that collects one quantity and drops off

            another at the same address is one job with both `pickup_load` and

            `delivery_load`.
        preferences:
          type:
            - array
            - 'null'
          items:
            $ref: '#/components/schemas/VehiclePreference'
          description: >-
            Soft vehicle affinity preferences, priced. Each entry is
            independent:

            a job listing several preferred vehicles it doesn't get pays each

            one's `violation_cost` (compounding, the same rule as a vehicle's

            `preferred_tags`).
        priority:
          type:
            - integer
            - 'null'
          format: int32
          description: >-
            Importance of a must-serve task when not all of them fit: the solver

            maximises the summed `priority` of served tasks before any cost. `>=
            1`,

            default `1` (maximise the count). A rank, not money — it never makes
            a

            task droppable; use `drop_cost` for that. Both set is a 400.
          example: 3
          minimum: 1
        required_skills:
          type:
            - array
            - 'null'
          items:
            $ref: '#/components/schemas/SkillRequirement'
          description: >-
            Hard skill requirements — the assigned vehicle must provide every
            one,

            at or above each requirement's `min_level`. A request may use at
            most

            256 distinct `(name, min_level)` pairs across all jobs and shipments

            (HTTP 400 `limit_exceeded` beyond that).
        service_duration_s:
          type:
            - integer
            - 'null'
          format: int32
          description: |-
            On-site service time paid per job (seconds).

            This is the nominal figure. The serving vehicle's `service_factor`
            scales it — the time actually scheduled is
            `service_duration_s / service_factor` — so the solution's
            `stops[].service_s` may differ from what you sent here. Default `0`;
            a negative value is rejected with HTTP 400.
          example: 300
          minimum: 0
        service_factors:
          type:
            - array
            - 'null'
          items:
            $ref: '#/components/schemas/ServiceFactor'
          description: >-
            Per-vehicle work-rate factors for this task's `service_duration_s`,

            which is DIVIDED by the factor (above `1` = faster). Each multiplies

            the serving vehicle's rate (the shift's `service_factor` if set,
            else

            the vehicle's): effective service time = `service_duration_s /

            (shift_or_vehicle_factor * factor)`. On a shipment the factor scales

            both legs. A vehicle not listed works at its own rate.
        setup_duration_s:
          type:
            - integer
            - 'null'
          format: int32
          description: |-
            Setup time paid once on arrival at a new location (seconds),
            distinct from `service_duration_s`. Skipped when the immediate real
            predecessor in the route already sits at this location — co-located
            stops share one setup.

            Not scaled by the vehicle's `service_factor`: setup is fixed arrival
            overhead (parking, walking to the door), not the work itself.
            Default `0`; a negative value is rejected with HTTP 400.
          example: 60
          minimum: 0
        tags:
          type:
            - array
            - 'null'
          items:
            type: string
          description: >-
            Free-form task labels (zones/territories, relation labels).

            Used for `tag`/relation resolution and, when a shift scopes itself
            via

            `serves_tags` (hard ANY-overlap) or a vehicle prices them via

            `preferred_tags` (soft), for tag matching. ANY-overlap — contrast
            with

            `required_skills`, which requires ALL. Not capability matching on
            its own.

            A request may carry at most 256 distinct tags across all jobs and

            shipments (HTTP 400 `limit_exceeded` beyond that); vehicle-side tag

            lists do not count.
        target:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/Target'
              description: |-
                Soft target for this task, measured at its arrival (default) or
                departure. On a shipment it is measured on the **delivery** leg.
          description: |-
            Soft target for this task, measured at its arrival (default) or
            departure. On a shipment it is measured on the **delivery** leg.
        time_windows:
          type:
            - array
            - 'null'
          items:
            $ref: '#/components/schemas/TimeWindow'
          description: Time windows for this visit.
      additionalProperties: false
      example:
        id: job-1
        location:
          - 3.725
          - 51.05
        pickup_load:
          weight: 20
        required_skills:
          - name: fridge
        service_duration_s: 300
        time_windows:
          - from: '2026-07-01T09:00:00+02:00'
            to: '2026-07-01T12:00:00+02:00'
    Objective:
      type: object
      description: |-
        Top-level objective configuration.

        Plans are compared in a fixed order: first **serve the required tasks**
        (the summed `priority` of the required tasks left unassigned is
        minimised), then **minimise cost** — the `costs` rates plus every priced
        preference, reported as `score.cost`. There is no separate vehicle-count
        step: to use fewer routes, price them with `costs.per_route`
        (per used shift); to use fewer distinct vehicles when they work several
        shifts, price them with `costs.per_used_vehicle` (once per vehicle).
      properties:
        costs:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/Costs'
              description: >-
                Fleet cost rates: the default for every vehicle and shift that
                does

                not set its own. See the `Costs` schema.
          description: |-
            Fleet cost rates: the default for every vehicle and shift that does
            not set its own. See the `Costs` schema.
        currency:
          type:
            - string
            - 'null'
          description: >-
            Currency label (ISO 4217, e.g. `EUR`) for every amount in the
            request

            and in `score`. Informational: echoed as `score.currency`, it never

            converts anything.
          example: EUR
        grouping:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/Grouping'
              description: Job-proximity cluster cohesion. See the `Grouping` schema.
          description: Job-proximity cluster cohesion. See the `Grouping` schema.
        per_delay_workday:
          type:
            - number
            - 'null'
          format: double
          description: >-
            ASAP scheduling: money charged for each served task per workday its

            shift lies after the earliest workday in the request, in the
            request's

            currency. A workday is a distinct calendar date, in the shift's own
            UTC

            offset, on which at least one shift starts; shifts on one date share
            a

            rank, and a date with no shift adds none. Unserved tasks pay
            nothing;

            the charge never outranks `priority` and cannot be combined with

            `drop_cost`. Absent or `0` = off; a single-date request is charged

            nothing. A problem that sets it, even on one date, solves without

            decomposition, like `assignment_costs`. Reported as
            `score.components.delay`. A positive

            rate below `0.00001` is rejected, as is a rate whose total over the

            later workdays exceeds `21474.83647` (`out_of_range`).
          example: 5
          minimum: 0
        per_late_hour:
          type:
            - number
            - 'null'
          format: double
          description: >-
            Cost per hour of lateness: the default rate for every task window

            that does not carry its own `time_windows[].per_late_hour`. Omit for

            hard windows. An explicit `0.0` is rejected as ambiguous (free

            lateness and hard windows read opposite ways, and lateness is never

            free). There is no earliness rate. It prices task windows only: a

            shift's `to` (plus `max_overtime_s` when set) stays a hard limit.

            Setting it `> 0` alongside a nonzero `time_windows[].choice_cost`
            anywhere in

            the request is rejected with HTTP 400.
          example: 60
          minimum: 0.0006
        workload_share:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/WorkloadShare'
              description: Spreads work across vehicles. See the `WorkloadShare` schema.
          description: Spreads work across vehicles. See the `WorkloadShare` schema.
      additionalProperties: false
      example:
        costs:
          per_route_hour: 35
          per_travel_km: 0.2
        currency: EUR
    Quota:
      type: object
      description: >-
        A hard cap on how many stops of `tasks` the `vehicles` serve together

        in a window. A stop counts when its service starts in `[from, to)`

        (`arrival + wait_s` in the response when no windowed break is taken on

        arrival there); without a window every stop counts.

        Stops count as `max_stops` counts them: a job one, a shipment two. An

        over-cap job is left unassigned. Unlike `limits.periods[]`, which counts
        a

        shift by its start, a quota has no `every` recurrence and caps only stop

        counts.
      required:
        - id
        - vehicles
        - tasks
        - max_stops
      properties:
        from:
          type:
            - string
            - 'null'
          format: date-time
          description: |-
            Window start (RFC 3339, UTC offset required), with `to`. The window
            must overlap a shift of one of `vehicles` (counting its
            `max_overtime_s`), else the request is rejected with HTTP 400.
          example: '2026-10-05T00:00:00+02:00'
        id:
          type: string
          description: |-
            Quota id: 1 to 128 characters, unique among `quotas`; named in the
            `/evaluate` `quota` violation.
          maxLength: 128
          minLength: 1
        max_stops:
          type: integer
          format: int32
          description: >-
            The cap on counted stops; a job counts as one stop.

            Start, end, break and reload stops in `routes[].stops` do not count;
            a

            shipment counts as two stops.
          minimum: 0
        tasks:
          type: array
          items:
            type: string
          description: |-
            Job or shipment ids whose stops count, each at most once. At least
            one; an id that names no job or shipment is rejected with HTTP 400.
          minItems: 1
        to:
          type:
            - string
            - 'null'
          format: date-time
          description: Window end (RFC 3339, UTC offset required, exclusive), with `from`.
          example: '2026-10-06T00:00:00+02:00'
        vehicles:
          type: array
          items:
            type: string
          description: >-
            Vehicle ids that share the cap, each at most once. At least one; an
            id

            that names no vehicle is rejected with HTTP 400.
          minItems: 1
      additionalProperties: false
      example:
        id: pool-install
        max_stops: 2
        tasks:
          - j1
          - j2
          - j3
        vehicles:
          - c0
          - c1
    Relation:
      oneOf:
        - $ref: '#/components/schemas/RelationSequence'
        - $ref: '#/components/schemas/RelationConsecutive'
        - $ref: '#/components/schemas/RelationTagOrder'
        - $ref: '#/components/schemas/RelationSameVehicle'
        - $ref: '#/components/schemas/RelationSameDay'
        - $ref: '#/components/schemas/RelationSameRoute'
        - $ref: '#/components/schemas/RelationSynchronized'
        - $ref: '#/components/schemas/RelationTimeLag'
        - $ref: '#/components/schemas/RelationCompatibleVehicles'
        - $ref: '#/components/schemas/RelationAllOrNone'
      description: >-
        A hard constraint between tasks, selected by `type`: `sequence`,

        `tag_order`, `same_vehicle`, `same_day`, `same_route`, `synchronized`,

        `time_lag`, `compatible_vehicles` or `all_or_none`.

        Each type carries only its own fields; any other `type` is rejected with

        HTTP 400.


        Members are named by `stops` (job ids, or shipment-leg stop ids where

        the type accepts them, see below). `same_vehicle`, `same_day` and

        `same_route` take **either** `stops` **or** a `tag`, not both. Every

        relation is hard.


        A shipment takes part through its legs: `sequence`, `same_vehicle`,

        `same_day` and `same_route` accept a leg's stop id (`pickup.id` /

        `delivery.id`, by default `"{shipment}:pickup"` / `":delivery"`) and
        bind

        that leg like a job. A leg coupled to other tasks by a `synchronized` or

        `time_lag` relation (directly, or through a `sequence` with a member
        that

        is) is rejected with HTTP 400, as is a `sequence` putting a delivery
        before

        its pickup. A shipment's own id in `stops`, or a shipment carrying the

        `tag` of a `same_vehicle` or `same_day` relation, is rejected with HTTP
        400

        `not_supported`: relations never silently skip a member. `same_route`,

        `time_lag`, `compatible_vehicles`, `all_or_none` and `tag_order` reject
        a

        shipment with HTTP 400 as well.
      example:
        stops:
          - job-1
          - job-2
        type: sequence
      discriminator:
        propertyName: type
        mapping:
          all_or_none: '#/components/schemas/RelationAllOrNone'
          compatible_vehicles: '#/components/schemas/RelationCompatibleVehicles'
          consecutive: '#/components/schemas/RelationConsecutive'
          same_day: '#/components/schemas/RelationSameDay'
          same_route: '#/components/schemas/RelationSameRoute'
          same_vehicle: '#/components/schemas/RelationSameVehicle'
          sequence: '#/components/schemas/RelationSequence'
          synchronized: '#/components/schemas/RelationSynchronized'
          tag_order: '#/components/schemas/RelationTagOrder'
          time_lag: '#/components/schemas/RelationTimeLag'
    Shipment:
      type: object
      description: >-
        A bound pickup→delivery shipment (the `shipments[]` entry type).


        Two stops served by one vehicle, pickup strictly before delivery, with

        `load` carried on the vehicle between the two legs.


        `drop_cost` and vehicle eligibility (`eligible_vehicles`, allow

        **and** exclude, any length) are fully supported and applied to both
        legs — a

        shipment's pickup and delivery share one droppability/eligibility
        decision,

        since one vehicle must serve both.
      required:
        - id
        - pickup
        - delivery
      properties:
        assignment_costs:
          type:
            - array
            - 'null'
          items:
            $ref: '#/components/schemas/AssignmentCost'
          description: >-
            Absolute price of serving this task on a given vehicle, in the

            objective's currency. Charged once, when that vehicle (on any of its

            shifts) serves the task; unlisted vehicles cost nothing. Unlike

            `preferences`, which charges the vehicles that did NOT get the task,

            this prices the vehicle that did, so a task rated badly everywhere
            is

            the first one dropped when capacity runs out. It lives in the cost
            tier,

            below `priority`, and adds to `preferences`, `drop_cost` and travel.

            Not applied by decomposition: a problem that sets it solves
            monolithically.
        delivery:
          $ref: '#/components/schemas/ShipmentStop'
          description: Delivery leg (load off).
        drop_cost:
          type:
            - number
            - 'null'
          format: double
          description: >-
            Drop **price** — what leaving this task unserved costs you, in the
            same

            currency as `objective.costs`, and the only way to make a task

            droppable. Tasks without a `drop_cost` must be served.


            This is money, not a priority: it enters the same cost the solver

            minimises, so the task is served only while serving it costs less
            than

            dropping it. Drop costs the plan chose to pay are reported in

            `score.components.drop_costs` and summed into `score.cost`.


            On a shipment the drop cost applies to the pair, charged once.


            Must be finite, `>= 0` and at most `1e9`; combined with `priority`
            it

            is rejected with HTTP 400 (`conflicting_fields`). Also rejected with

            HTTP 400 (`not_supported`) while a positive
            `objective.per_delay_workday` is set,

            since a droppable task could be dropped to avoid its delay charge.
          example: 25
          maximum: 1000000000
          minimum: 0
        eligible_vehicles:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/VehicleEligibility'
              description: >-
                Hard vehicle allow/exclude. A request may use at most 256
                distinct

                permitted-vehicle sets (after resolving `allowed`/`excluded`)
                across

                all jobs and shipments (HTTP 400 `limit_exceeded` beyond that).
          description: |-
            Hard vehicle allow/exclude. A request may use at most 256 distinct
            permitted-vehicle sets (after resolving `allowed`/`excluded`) across
            all jobs and shipments (HTTP 400 `limit_exceeded` beyond that).
        id:
          type: string
          description: >-
            Shipment id: 1 to 128 characters, unique across `jobs` +
            `shipments`.
          example: ship-1
          maxLength: 128
          minLength: 1
        load:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/Quantities'
              description: >-
                Load carried from the pickup to the delivery: picked up at one
                leg and

                the same quantity dropped off at the other. Non-negative
                magnitudes

                per named dimension.
          description: >-
            Load carried from the pickup to the delivery: picked up at one leg
            and

            the same quantity dropped off at the other. Non-negative magnitudes

            per named dimension.
        pickup:
          $ref: '#/components/schemas/ShipmentStop'
          description: Pickup leg (load on).
        preferences:
          type:
            - array
            - 'null'
          items:
            $ref: '#/components/schemas/VehiclePreference'
          description: >-
            Soft vehicle affinity preferences, priced. Each entry is
            independent:

            a job listing several preferred vehicles it doesn't get pays each

            one's `violation_cost` (compounding, the same rule as a vehicle's

            `preferred_tags`).
        priority:
          type:
            - integer
            - 'null'
          format: int32
          description: >-
            Importance of a must-serve task when not all of them fit: the solver

            maximises the summed `priority` of served tasks before any cost. `>=
            1`,

            default `1` (maximise the count). A rank, not money — it never makes
            a

            task droppable; use `drop_cost` for that. Both set is a 400.
          example: 3
          minimum: 1
        required_skills:
          type:
            - array
            - 'null'
          items:
            $ref: '#/components/schemas/SkillRequirement'
          description: >-
            Hard skill requirements — the assigned vehicle must provide every
            one,

            at or above each requirement's `min_level`. A request may use at
            most

            256 distinct `(name, min_level)` pairs across all jobs and shipments

            (HTTP 400 `limit_exceeded` beyond that).
        service_factors:
          type:
            - array
            - 'null'
          items:
            $ref: '#/components/schemas/ServiceFactor'
          description: >-
            Per-vehicle work-rate factors for this task's `service_duration_s`,

            which is DIVIDED by the factor (above `1` = faster). Each multiplies

            the serving vehicle's rate (the shift's `service_factor` if set,
            else

            the vehicle's): effective service time = `service_duration_s /

            (shift_or_vehicle_factor * factor)`. On a shipment the factor scales

            both legs. A vehicle not listed works at its own rate.
        tags:
          type:
            - array
            - 'null'
          items:
            type: string
          description: >-
            Free-form task labels (zones/territories, relation labels).

            Used for `tag`/relation resolution and, when a shift scopes itself
            via

            `serves_tags` (hard ANY-overlap) or a vehicle prices them via

            `preferred_tags` (soft), for tag matching. ANY-overlap — contrast
            with

            `required_skills`, which requires ALL. Not capability matching on
            its own.

            A request may carry at most 256 distinct tags across all jobs and

            shipments (HTTP 400 `limit_exceeded` beyond that); vehicle-side tag

            lists do not count.
        target:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/Target'
              description: |-
                Soft target for this task, measured at its arrival (default) or
                departure. On a shipment it is measured on the **delivery** leg.
          description: |-
            Soft target for this task, measured at its arrival (default) or
            departure. On a shipment it is measured on the **delivery** leg.
      additionalProperties: false
      example:
        delivery:
          location:
            - 3.71
            - 51.06
          service_duration_s: 300
        id: ship-1
        load:
          weight: 20
        pickup:
          location:
            - 3.725
            - 51.05
          service_duration_s: 300
    Travel:
      type: object
      description: >-
        Where travel times and distances come from.


        Omitted altogether, travel times are free-flow (`osm`): the same request

        prices the same at any hour. Set `departure_time` to plan against an

        instant's traffic — e.g. tomorrow's 08:00 routes with morning-rush
        travel

        times, or a replay of a past run under its original conditions; the
        engine

        is then `tomtom`. Set `engine: tomtom` alone for traffic at the moment
        the

        request is processed. `engine: "straight_line"` skips the road network
        for

        great-circle travel at `speed_kmh`.
      properties:
        departure_time:
          type:
            - string
            - 'null'
          format: date-time
          description: >-
            Departure time for the traffic lookup (RFC 3339 instant, any
            offset).

            Omitted → free-flow times, or traffic at solve time with `engine:

            tomtom`. An explicit departure time bypasses the server-side matrix

            cache.
          example: '2026-07-01T08:00:00+02:00'
        engine:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/MatrixEngine'
              description: >-
                Data behind travel times and distances. Omitted → `osm`, or
                `tomtom`

                when `departure_time` is set.
          description: |-
            Data behind travel times and distances. Omitted → `osm`, or `tomtom`
            when `departure_time` is set.
        speed_kmh:
          type:
            - number
            - 'null'
          format: double
          description: >-
            Travel speed for `engine: "straight_line"`, in km/h: each leg takes
            its

            great-circle distance at this speed, rounded to the whole second.
            `1` to

            `1000`, default `50`; `vehicles[].speed_factor` still scales it per

            vehicle. Any other engine rejects it (HTTP 400
            `conflicting_fields`).
          default: 50
          example: 50
          maximum: 1000
          minimum: 1
      additionalProperties: false
      example:
        departure_time: '2026-07-01T08:00:00+02:00'
        engine: tomtom
    Vehicle:
      type: object
      description: A vehicle type available to serve tasks.
      required:
        - id
        - shifts
      properties:
        capacity:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/Quantities'
              description: Named capacity dimensions.
          description: Named capacity dimensions.
        compatible_with:
          type:
            - array
            - 'null'
          items:
            type: string
          description: >-
            Vehicles this one may serve a `compatible_vehicles` relation's tasks

            together with, by id. Absent: any vehicle. A list is a veto, so a

            one-sided list still binds both vehicles: two vehicles may share a

            relation only if neither's list excludes the other. A vehicle always

            pairs with itself and all its shifts count as one vehicle; to pair a

            vehicle with no other, list only its own id. An empty list is
            rejected

            with HTTP 400 (it would read as "everyone" in a client that drops
            empty

            arrays), and so is an id that names no vehicle. Inert unless a

            `compatible_vehicles` relation names tasks.
          example:
            - v2
            - v3
          minItems: 1
        costs:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/Costs'
              description: >-
                This vehicle's costs. Each field set here replaces the same
                field of

                `objective.costs` for this vehicle; fields not set keep the
                fleet

                value. A shift's own `costs` replace these per field in turn.
          description: |-
            This vehicle's costs. Each field set here replaces the same field of
            `objective.costs` for this vehicle; fields not set keep the fleet
            value. A shift's own `costs` replace these per field in turn.
        id:
          type: string
          description: >-
            Vehicle id: 1 to 128 characters, unique across `vehicles[]`; echoed
            as

            `routes[].vehicle`.
          example: v1
          maxLength: 128
          minLength: 1
        limits:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/VehicleLimits'
              description: Hard operating limits.
          description: Hard operating limits.
        preferred_tags:
          type:
            - array
            - 'null'
          items:
            $ref: '#/components/schemas/TagPreference'
          description: >-
            Soft tag preferences. Serving a task that does not

            carry a preferred tag incurs that entry's `violation_cost`. Entries

            apply independently: a task lacking several preferred tags incurs
            each

            cost.
        profile:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/Profile'
              description: >-
                Routing profile: `car` (default), `truck` or `bike` — selects
                the road

                network and speeds behind this vehicle's travel times and
                distances.

                Any other value is rejected with HTTP 400.
          description: >-
            Routing profile: `car` (default), `truck` or `bike` — selects the
            road

            network and speeds behind this vehicle's travel times and distances.

            Any other value is rejected with HTTP 400.
        service_factor:
          type:
            - number
            - 'null'
          format: double
          description: >-
            Work-rate multiplier for service time at stops (1.0 = base rate).


            Same direction as `speed_factor`: effective service time =

            `service_duration_s / service_factor`, so values > 1.0 model a
            driver who

            works FASTER and spends less time at each stop, and < 1.0 one who is

            slower. Use it for crew size, equipment, or experience — how quickly
            the

            work gets done, as distinct from how fast the vehicle drives.


            Applies to job/stop `service_duration_s` only. Deliberately NOT
            applied

            to `setup_duration_s` (fixed arrival overhead such as parking),

            depot handling (`depots[].service_duration_s`,

            `start`/`end.service_duration_s`), waiting time, or break durations.


            Must be in `[0.001, 5]`. Default 1.0. A shift's own `service_factor`

            replaces it for that shift; a task's `service_factors[].factor` for

            this vehicle multiplies whichever applies: effective service time =

            `service_duration_s / (shift_or_vehicle_factor * task_factor)`.


            V2's `durationModifier` went the other way (it MULTIPLIED duration,

            `0.8` = 20% faster); the V2 compatibility layer inverts it and sends
            it

            as the task's `service_factors[].factor = 1 / durationModifier`.
          default: 1
          maximum: 5
          minimum: 0.001
        shifts:
          type: array
          items:
            $ref: '#/components/schemas/Shift'
          description: >-
            One or more working shifts. A vehicle without shifts is rejected
            with

            HTTP 400.
          minItems: 1
        skills:
          type:
            - array
            - 'null'
          items:
            $ref: '#/components/schemas/VehicleSkill'
          description: >-
            Capabilities the vehicle provides. A job's `required_skills` must
            all appear

            here at a level of at least the job's `min_level`. Each entry is an

            object `{ "name": ..., "level": N }` (`level` optional, default 1);

            a bare string is rejected. A skill may appear once per list.
            Omitting

            the field is the same as `[]`: the vehicle can only serve jobs that

            require no skills.
        speed_factor:
          type:
            - number
            - 'null'
          format: double
          description: >-
            Travel-speed multiplier (1.0 = base speed). Effective travel time =

            `base travel time / speed_factor`, so values > 1.0 model a faster

            vehicle and < 1.0 a slower one. Must be in `[0.001, 5]`. Default
            1.0.


            Covers driving only. Its companion `service_factor` scales the

            work done at stops; the two are independent and share this direction
            and

            range.
          default: 1
          maximum: 5
          minimum: 0.001
        workload_weight:
          type:
            - number
            - 'null'
          format: double
          description: >-
            Share of the work this vehicle carries under
            `objective.workload_share`,

            relative to the other vehicles. Default `1`; `0.5` carries half as
            much;

            `0` leaves the vehicle out of the balancing. `0` to `1000000`

            (HTTP 400 `out_of_range` outside that). Ignored when

            `objective.workload_share` is not set.
          example: 0.5
          maximum: 1000000
          minimum: 0
      additionalProperties: false
      example:
        capacity:
          volume: 20
          weight: 500
        id: v1
        profile: car
        shifts:
          - end:
              location:
                - 3.7174
                - 51.0543
            from: '2026-07-01T08:00:00+02:00'
            id: mon
            start:
              location:
                - 3.7174
                - 51.0543
            to: '2026-07-01T17:00:00+02:00'
        skills:
          - name: fridge
    ErrorItemCode:
      type: string
      description: >-
        The `code` of one `errors[]` item. Clients should accept values they do

        not know yet.


        Known values: `invalid_json`, `invalid_type`, `unknown_field`,
        `missing_field`, `invalid_value`, `not_supported`, `out_of_range`,
        `duplicate_id`, `unknown_reference`, `conflicting_fields`,
        `limit_exceeded`. An open set: a later release may add values, so accept
        any string and handle the ones you know.
      x-extensible-enum:
        - invalid_json
        - invalid_type
        - unknown_field
        - missing_field
        - invalid_value
        - not_supported
        - out_of_range
        - duplicate_id
        - unknown_reference
        - conflicting_fields
        - limit_exceeded
    PlanStop:
      oneOf:
        - $ref: '#/components/schemas/PlanStopJob'
        - $ref: '#/components/schemas/PlanStopPickup'
        - $ref: '#/components/schemas/PlanStopDelivery'
        - $ref: '#/components/schemas/PlanStopReload'
      description: |-
        One stop of a plan route. Start, end and breaks are placed by the solver
        and are not listed.
      example:
        id: parts-9:pickup
        type: pickup
      discriminator:
        propertyName: type
        mapping:
          delivery: '#/components/schemas/PlanStopDelivery'
          job: '#/components/schemas/PlanStopJob'
          pickup: '#/components/schemas/PlanStopPickup'
          reload: '#/components/schemas/PlanStopReload'
    Place:
      type: array
      items:
        type: number
        format: double
      description: >-
        A place: a coordinate `[longitude, latitude]` in GeoJSON order — exactly
        two numbers. Tasks at the same coordinate are treated as one location.
        Longitude must lie in `[-180, 180]` and latitude in `[-90, 90]` (HTTP
        400 `out_of_range` otherwise), and `[0, 0]` is rejected with HTTP 400
        `invalid_value` as a missing-GPS placeholder.
      examples:
        - - 3.725
          - 51.05
      maxItems: 2
      minItems: 2
    AssignmentCost:
      type: object
      description: Absolute price of serving one task on one vehicle.
      required:
        - vehicle
        - cost
      properties:
        cost:
          type: number
          format: double
          description: |-
            Cost, in the objective's currency, incurred when this vehicle serves
            the task. Must be finite and `>= 0`. A vehicle not listed costs `0`.
          example: 12.5
          maximum: 21474.83647
          minimum: 0
        vehicle:
          type: string
          description: >-
            Vehicle id; applies to every shift of that vehicle. An id that
            matches

            no vehicle, or a vehicle listed twice in one task's

            `assignment_costs`, is rejected with HTTP 400.
          example: v1
      additionalProperties: false
      example:
        cost: 12.5
        vehicle: v1
    Quantities:
      type: object
      description: >-
        Named capacity/demand/load dimensions, e.g. `{"weight": 30, "volume":
        2}`. Keys are user-chosen dimension names; values are integer
        quantities. Order-independent and self-documenting. In a request every
        value must be `>= 0` (a negative one is rejected with HTTP 400
        `out_of_range`), and a request may use at most 4 distinct dimension
        names across jobs, shipments and vehicles (HTTP 400 `limit_exceeded`
        beyond that).
      additionalProperties:
        type: integer
        format: int32
        minimum: 0
      propertyNames:
        type: string
      example:
        volume: 2
        weight: 20
    VehicleEligibility:
      type: object
      description: 'Hard vehicle eligibility: whitelist or blacklist (mutually exclusive).'
      properties:
        allowed:
          type:
            - array
            - 'null'
          items:
            type: string
          description: |-
            Hard whitelist of vehicle ids. Omit = all vehicles allowed. An empty
            list, or an id that names no vehicle, is rejected with HTTP 400.
          minItems: 1
        excluded:
          type:
            - array
            - 'null'
          items:
            type: string
          description: |-
            Hard blacklist of vehicle ids. Mutually exclusive with `allowed`. An
            id that names no vehicle is rejected with HTTP 400.
      additionalProperties: false
      example:
        allowed:
          - v1
          - v2
      not:
        not:
          anyOf:
            - properties:
                allowed:
                  type: 'null'
              required:
                - allowed
            - properties:
                excluded:
                  type: 'null'
              required:
                - excluded
        required:
          - allowed
          - excluded
    VehiclePreference:
      type: object
      description: A soft vehicle affinity preference, priced.
      required:
        - vehicle
        - violation_cost
      properties:
        vehicle:
          type: string
          description: >-
            Preferred vehicle id. An id that names no vehicle, or a vehicle
            listed

            twice in one task's `preferences`, is rejected with HTTP 400.
          example: v1
        violation_cost:
          type: number
          format: double
          description: |-
            Cost, in the objective's currency, incurred when a different vehicle
            serves the job. Must be finite and `>= 0`.
          example: 50
          maximum: 21474.83647
          minimum: 0
      additionalProperties: false
      example:
        vehicle: v1
        violation_cost: 50
    SkillRequirement:
      type: object
      description: A hard skill requirement — the assigned vehicle must provide it.
      required:
        - name
      properties:
        min_level:
          type:
            - integer
            - 'null'
          format: int32
          description: |-
            Minimum level the serving vehicle must hold for this skill (`>= 1`).
            Absent = 1. A vehicle skill with no `level` holds level 1.
          example: 2
          minimum: 1
        name:
          type: string
          description: Skill name the assigned vehicle must provide.
          example: fridge
      additionalProperties: false
      example:
        name: fridge
    ServiceFactor:
      type: object
      description: Per-vehicle work-rate override for one task's `service_duration_s`.
      required:
        - vehicle
        - factor
      properties:
        factor:
          type: number
          format: double
          description: >-
            Work-rate factor of `vehicle` on this task. Service time is DIVIDED
            by

            it: above `1` the vehicle works faster here, below `1` slower; `1.0`
            is a

            no-op. It multiplies the vehicle's own rate (the shift's

            `service_factor` if set, else the vehicle's): effective service time
            =

            `service_duration_s / (shift_or_vehicle_factor * factor)`. Same

            direction and `[0.001, 5]` range as `vehicles[].service_factor`.
            V2's

            `proficiency.durationModifier` multiplied duration instead; the V2

            compatibility layer sends its inverse (`1 / durationModifier`) here.
          example: 1.25
          maximum: 5
          minimum: 0.001
        vehicle:
          type: string
          description: Vehicle id; applies to every shift of that vehicle.
          example: v1
      additionalProperties: false
      example:
        factor: 1.25
        vehicle: v1
    Target:
      type: object
      description: |-
        Soft target: reaching the task's `on` instant after `at` is allowed but
        priced. Distinct from a hard time window — a target is a point, never
        causes infeasibility, and (unlike a window's `from`) never forces the
        vehicle to wait when it is early.
      required:
        - at
        - per_late_hour
      properties:
        at:
          type: string
          format: date-time
          description: >-
            Target instant (RFC 3339, UTC offset required) the `on` instant is
            measured against.
          example: '2026-07-01T10:00:00+02:00'
        'on':
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/TargetInstant'
              description: >-
                Which instant of the task is compared to `at`: `arrival`
                (default) or

                `departure`, the stop fields of the same names.
          description: >-
            Which instant of the task is compared to `at`: `arrival` (default)
            or

            `departure`, the stop fields of the same names.
          default: arrival
        per_late_hour:
          type: number
          format: double
          description: |-
            Cost, in the objective's currency, per hour of `on` beyond `at`
            (linear, prorated per second). Must be `> 0`; independent of
            `objective.per_late_hour` (which prices *time-window* lateness,
            not a target point).
          example: 60
          maximum: 21474.83647
          minimum: 0.00001
      additionalProperties: false
      example:
        at: '2026-07-01T10:00:00+02:00'
        'on': arrival
        per_late_hour: 60
    TimeWindow:
      type: object
      description: |-
        A time window. Hard unless it (or `objective.per_late_hour`, for
        every window) prices lateness.
      required:
        - from
        - to
      properties:
        choice_cost:
          type:
            - number
            - 'null'
          format: double
          description: >-
            Optional flat cost, in the objective's currency, added when this

            job/stop is served within THIS window rather than a free/cheaper

            alternative window on the same task. Absent = 0 (no charge). Every

            window in the list remains individually hard — a job must still land

            in *some* window — this only grades preference among the hard

            alternatives; it does not soften time-window feasibility. Must be

            finite and `>= 0`, and requires at least 2 windows on the task. Once

            any window on the task carries a *nonzero* cost, that task's windows

            must also be pairwise non-overlapping, and windows that only touch

            count as overlapping (a task whose costs are all

            `0`/absent has its overlapping windows merged).


            **Cannot be combined with `objective.per_late_hour > 0`** — a

            nonzero `choice_cost` anywhere in the request alongside priced
            lateness

            is rejected with HTTP 400 (the two read opposite ways about which

            window was paid for). Choose one: priced alternative windows, or
            priced

            lateness.
          maximum: 21474.83647
          minimum: 0
        from:
          type: string
          format: date-time
          description: |-
            Earliest allowed start of service (RFC 3339 instant, UTC offset
            required). A vehicle that arrives earlier waits.
          example: '2026-07-01T09:00:00+02:00'
        per_late_hour:
          type:
            - number
            - 'null'
          format: double
          description: >-
            Makes this window soft on its late side: arriving after `to` is

            allowed and priced at this rate, in the objective's currency per
            hour

            late, in place of `objective.per_late_hour` (and whether or not

            that is set, so hard and soft windows can share one request). Must
            be

            `> 0`; omit it for a hard window. Only on a

            task with exactly one window. Arriving early still waits for `from`.
          example: 60
          minimum: 0.0006
        to:
          type: string
          format: date-time
          description: |-
            Latest allowed start of service (RFC 3339 instant, UTC offset
            required), measured from arrival plus any windowed break taken first
            (#1750 D1). Hard unless `per_late_hour` prices lateness.
          example: '2026-07-01T12:00:00+02:00'
      additionalProperties: false
      example:
        choice_cost: 15
        from: '2026-07-01T09:00:00+02:00'
        to: '2026-07-01T12:00:00+02:00'
    Costs:
      type: object
      description: >-
        What running a vehicle costs, in the request's currency

        (`objective.currency` is only a label). `objective.costs` prices the
        fleet

        and `vehicles[].costs` replaces a field for one vehicle;
        `shifts[].costs`

        is the same without `per_used_vehicle` (schema `ShiftCosts`), and
        replaces

        a field for one shift. Each field resolves on its own: the shift's
        value,

        else the vehicle's, else the fleet's, else the default. Nothing is added

        on top of a fleet rate, and every field is reported under the same

        `score.components` key at every level. `0` means priced at zero; absent

        means inherit.


        Rates are rounded to the internal grid (1/100 000 of a currency unit per

        second or per metre); a nonzero rate too small to represent is rejected.

        Every value must be finite and `>= 0`.


        Lateness is not priced here: `objective.per_late_hour` (or a window's
        own

        `per_late_hour`) is the rate, because it softens a constraint rather
        than

        pricing a resource.
      properties:
        per_overtime_hour:
          type:
            - number
            - 'null'
          format: double
          description: >-
            Per hour a route finishes past its shift's `to` (possible only with

            `max_overtime_s`), reported in `score.components.overtime`, on top
            of

            `per_route_hour`. Default `0`: overtime is unpriced.
          example: 0
          minimum: 0
        per_route:
          type:
            - number
            - 'null'
          format: double
          description: >-
            Flat charge for each used shift (a route with at least one task),

            reported in `score.components.routes`. Default `0`. The higher it
            is,

            the more travel the solver accepts to avoid opening another route.
            It

            counts shifts, not vehicles: a vehicle that works five days pays it
            five

            times, so it does not prefer one vehicle on two shifts over two
            vehicles

            on one shift each; use `per_used_vehicle` for that.
          example: 50
          maximum: 1000000000
          minimum: 0
        per_route_hour:
          type:
            - number
            - 'null'
          format: double
          description: >-
            Per hour of each route's wall-clock span (travel, service, waiting

            and breaks), reported in `score.components.time`. Default **35** at
            objective

            level; a vehicle or shift value inherits the fleet value.
          example: 35
          minimum: 0
        per_travel_km:
          type:
            - number
            - 'null'
          format: double
          description: >-
            Per kilometre driven, reported in `score.components.distance`.

            Default **0.20** at objective level; a vehicle or shift value
            inherits the

            fleet value.
          example: 0.2
          minimum: 0
        per_used_vehicle:
          type:
            - number
            - 'null'
          format: double
          description: >-
            Flat charge once per vehicle that serves at least one task, however

            many of its shifts it works. Reported in
            `score.components.vehicles`.

            Adds to `per_route`: the vehicle's first used shift pays both, later

            shifts only `per_route`. Default `0`. Use it to minimise distinct

            vehicles; use `per_route` to price each working day. Not accepted on

            `shifts[].costs`. When any vehicle has a non-zero rate and any
            vehicle

            has several shifts, the problem solves without decomposition.
          example: 400
          maximum: 1000000000
          minimum: 0
        per_wait_hour:
          type:
            - number
            - 'null'
          format: double
          description: |-
            Per hour spent waiting for a time window to open, reported in
            `score.components.waiting`, on top of `per_route_hour` (the span
            includes the wait). Idle before a windowed break's window opens is
            reported in `wait_s` but not charged here. Default `0`.
          example: 0
          minimum: 0
      additionalProperties: false
      example:
        per_route: 50
        per_route_hour: 35
        per_travel_km: 0.2
    Grouping:
      type: object
      description: >-
        Keep geographically clustered tasks consecutive in a route.


        Two tasks are in the same **group** when they lie within `radius_m` of

        each other. The solver adds `per_entry` to the objective each time a

        route enters a group from a job outside it, the first entry included;

        entering from the shift's start or from a reload is free. Keeping each

        group in one visit minimises the "passing-waypoint" / area re-entry
        count

        last-mile fleets track. Presence of this object enables the feature;
        omit

        it to disable.
      required:
        - per_entry
      properties:
        distance_metric:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/GroupingDistance'
              description: How neighbour distance is measured. Default `"straight_line"`.
          description: How neighbour distance is measured. Default `"straight_line"`.
          default: straight_line
        per_entry:
          type: number
          format: double
          description: >-
            Cost added to the objective on every entry into a group from a job

            outside it (a job that is not within `radius_m` of the entered one),

            the first entry included, in the objective's currency. Entry from
            the

            shift's start or from a reload is free. At most 1000 currency units:
            a

            larger value is rejected with HTTP 400. Higher keeps clusters
            together

            more aggressively at the expense of total travel.
          example: 0.5
          maximum: 1000
          minimum: 0
        radius_m:
          type:
            - integer
            - 'null'
          format: int32
          description: Radius defining a group, in metres. Default `180`. Must be `> 0`.
          default: 180
          example: 180
          minimum: 1
      additionalProperties: false
      example:
        distance_metric: straight_line
        per_entry: 0.5
        radius_m: 180
    WorkloadShare:
      type: object
      description: >-
        Spreads work across vehicles: each vehicle's scheduled service time is

        compared with its share of all the work, and the part outside a
        tolerance

        band is charged.


        The balanced quantity is **service time only**: every served task's

        `service_duration_s`, scaled by the vehicle's `service_factor`. Travel,

        `setup_duration_s`, waiting and breaks are not counted, and a task-level

        `service_factors` entry scales the measure but not the target. Shares
        are

        fixed when the request is read: every task counts towards the total,

        served or not, so with many unassigned tasks every vehicle sits under
        its

        share and the term pulls work in without ordering the routes between
        them;

        balancing pressure appears once routes enter their band. A vehicle with

        several shifts has its share split across them by the work each shift
        can

        reach (the service time of the tasks whose windows overlap it), weighted

        by shift length, so a day with no tasks carries none of the share; each

        shift is charged on its own part. An empty, non-exempt shift pays for
        its whole share, so pair the

        term with `costs.per_route` or `per_used_vehicle` only when both are
        meant.

        See `vehicles[].workload_weight` to give a part-time vehicle a smaller

        share. A problem that sets the term solves without decomposition, like

        `assignment_costs`.
      required:
        - per_imbalance_hour
      properties:
        per_imbalance_hour:
          type: number
          format: double
          description: |-
            Money per hour a vehicle's service time is outside the band, in the
            request's currency. Greater than 0 and at least 0.036 (the internal
            resolution); omit `workload_share` to switch the term off.
          example: 35
          exclusiveMinimum: 0
        tolerance:
          type:
            - number
            - 'null'
          format: double
          description: >-
            Half-width of the band as a fraction of the vehicle's share, `0` up
            to

            but not including `1`. Default `0.1`: a vehicle within 10% of its
            share

            is not charged. The charge grows from the band edge.
          example: 0.1
          minimum: 0
          exclusiveMaximum: 1
      additionalProperties: false
    RelationSequence:
      type: object
      title: Sequence
      description: |-
        Ordering (gaps allowed). Constrains members that end up on the **same
        route** only: members on different vehicles are unordered relative to
        each other.
      required:
        - stops
        - type
      properties:
        stops:
          type: array
          items:
            type: string
          description: |-
            Job ids or shipment-leg stop ids (`pickup.id` / `delivery.id`, by
            default `"{shipment}:pickup"` / `"{shipment}:delivery"`), in the
            order they must be served. A shipment's own id is
            rejected with HTTP 400 `not_supported`.
        type:
          type: string
          description: 'Discriminator: always `sequence`.'
          enum:
            - sequence
      additionalProperties: false
    RelationConsecutive:
      type: object
      title: Consecutive
      description: |-
        Ordering with no gap: like `sequence`, and in addition no other stop may
        sit between two neighbouring members that are served on the same
        route. A member that is unassigned is skipped, so only listed
        neighbours that are both served are adjacent. Members on different
        routes are unconstrained; add `same_route` to keep them together.
      required:
        - stops
        - type
      properties:
        stops:
          type: array
          items:
            type: string
          description: >-
            Job ids or shipment-leg stop ids, in the order they must be served

            back to back. At least 2. A stop has one successor and one
            predecessor

            across all `consecutive` relations, so `[a, b]` and `[b, c]` may

            overlap but `[a, b]` and `[a, c]` may not. A shipment's own id is
            rejected with HTTP

            400 `not_supported`.
          minItems: 2
        type:
          type: string
          description: 'Discriminator: always `consecutive`.'
          enum:
            - consecutive
      additionalProperties: false
    RelationTagOrder:
      type: object
      title: Tag order
      description: |-
        Ordering between tags: on every route, every task carrying `tags[0]`
        precedes every task carrying `tags[1]`, and so on. Tasks carry
        membership via their own `tags`. At most one `tag_order` per request.

        Jobs only: if a tag named here selects a **shipment**, the request is
        rejected with HTTP 400 (a shipment's two legs already carry their own
        hard precedence, which tag ordering cannot compose with yet). Drop the
        tag from the shipment.
      required:
        - tags
        - type
      properties:
        tags:
          type: array
          items:
            type: string
          description: Tag labels in precedence order. Requires at least 2 entries.
          minItems: 2
        type:
          type: string
          description: 'Discriminator: always `tag_order`.'
          enum:
            - tag_order
      additionalProperties: false
    RelationSameVehicle:
      type: object
      title: Same vehicle
      description: |-
        Same vehicle: every member that is served is served by the same
        vehicle, on any of its shifts.
      required:
        - type
      properties:
        stops:
          type:
            - array
            - 'null'
          items:
            type: string
          description: |-
            Job ids or shipment-leg stop ids in the relation (mutually
            exclusive with `tag`). A shipment's own id is rejected with
            HTTP 400 `not_supported`.
        tag:
          type:
            - string
            - 'null'
          description: >-
            Tag selecting the relation members (mutually exclusive with
            `stops`).
        type:
          type: string
          description: 'Discriminator: always `same_vehicle`.'
          enum:
            - same_vehicle
      additionalProperties: false
      oneOf:
        - not:
            anyOf:
              - properties:
                  stops:
                    type: 'null'
                required:
                  - stops
          required:
            - stops
        - not:
            anyOf:
              - properties:
                  tag:
                    type: 'null'
                required:
                  - tag
          required:
            - tag
    RelationSameDay:
      type: object
      title: Same day
      description: |-
        Same calendar day: every member that is served is served on the same
        day. The day is the calendar date of the serving shift's `from`, read
        in that value's own UTC offset, not the time of the stop. Like
        `same_vehicle`, it constrains only the members that end up assigned.
      required:
        - type
      properties:
        stops:
          type:
            - array
            - 'null'
          items:
            type: string
          description: |-
            Job ids or shipment-leg stop ids in the relation (mutually
            exclusive with `tag`). A shipment's own id is rejected with
            HTTP 400 `not_supported`.
        tag:
          type:
            - string
            - 'null'
          description: >-
            Tag selecting the relation members (mutually exclusive with
            `stops`).
        type:
          type: string
          description: 'Discriminator: always `same_day`.'
          enum:
            - same_day
      additionalProperties: false
      oneOf:
        - not:
            anyOf:
              - properties:
                  stops:
                    type: 'null'
                required:
                  - stops
          required:
            - stops
        - not:
            anyOf:
              - properties:
                  tag:
                    type: 'null'
                required:
                  - tag
          required:
            - tag
    RelationSameRoute:
      type: object
      title: Same route
      description: |-
        Same route: every member that ends up assigned is served by one
        vehicle shift (one entry of `shifts`), stricter than
        `same_vehicle` for vehicles with several `shifts`. Reloads within
        the shift do not split a route. Hard-only, with `same_vehicle`'s
        partial-service semantics.
      required:
        - type
      properties:
        stops:
          type:
            - array
            - 'null'
          items:
            type: string
          description: |-
            Job ids or shipment-leg stop ids in the relation (mutually
            exclusive with `tag`). A shipment's own id is rejected with HTTP
            400.
        tag:
          type:
            - string
            - 'null'
          description: >-
            Tag selecting the relation members (mutually exclusive with
            `stops`).
        type:
          type: string
          description: 'Discriminator: always `same_route`.'
          enum:
            - same_route
      additionalProperties: false
      oneOf:
        - not:
            anyOf:
              - properties:
                  stops:
                    type: 'null'
                required:
                  - stops
          required:
            - stops
        - not:
            anyOf:
              - properties:
                  tag:
                    type: 'null'
                required:
                  - tag
          required:
            - tag
    RelationSynchronized:
      type: object
      title: Synchronized
      description: |-
        Synchronized starts (or ends) across vehicles: every member's
        service start — or with `on: departure`, its service end — lies within
        `max_spread_s` seconds of every other member's. Hard, checked across
        routes; the returned schedule holds a member back where needed so
        the bound is met (see `hold_s` on the stop). A hold waits inside the
        regular shift only: a member is never held past its shift's `to`
        into overtime (`max_overtime_s`); a group that could only meet the
        bound that way is left unassigned instead.
      required:
        - max_spread_s
        - stops
        - type
      properties:
        max_spread_s:
          type: integer
          format: int64
          description: |-
            Maximum spread between the synchronized instants in seconds
            (`0` = exact simultaneity). Required. A negative value, or one that
            does not fit in 32 bits, is rejected with HTTP 400 `out_of_range`.
          maximum: 2147483647
          minimum: 0
        'on':
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/RelationInstant'
              description: >-
                Which instant is synchronized: `service_start` (default) or

                `departure` (the service end), the stop fields of the same
                names.
          description: |-
            Which instant is synchronized: `service_start` (default) or
            `departure` (the service end), the stop fields of the same names.
          default: service_start
        stops:
          type: array
          items:
            type: string
          description: |-
            Participating job ids, at most 8 (HTTP 400 `limit_exceeded`
            beyond that). A shipment-leg stop id is rejected with HTTP 400;
            a shipment's own id with HTTP 400 `not_supported`.
          maxItems: 8
        type:
          type: string
          description: 'Discriminator: always `synchronized`.'
          enum:
            - synchronized
      additionalProperties: false
    RelationTimeLag:
      type: object
      title: Time lag
      description: >-
        A directional time bound between two jobs on any vehicles:

        `t(to) − t(from)` must lie in `[min_s, max_s]` seconds, each end

        measured at the job's service start or end (`on`). "B starts at

        least an hour after A ends" is `from: {stop: A, on: departure}, to:
        {stop: B},

        min_s: 3600`; "A and B finish together" is both ends `on: departure`
        with

        `min_s: 0, max_s: 0`. `min_s` defaults to `0`; an absent `max_s` is

        unbounded; both may be negative. Hard, checked across routes like

        `synchronized` (an unassigned end imposes nothing). A job may take

        part in any number of `time_lag` relations; a chain is written as

        one relation per consecutive pair. As with `synchronized`, a hold

        never waits past the shift's regular end (`to`).
      required:
        - from
        - to
        - type
      properties:
        from:
          $ref: '#/components/schemas/TimeLagMember'
          description: The reference end.
        max_s:
          type:
            - integer
            - 'null'
          format: int64
          description: |-
            Upper bound on `t(to) − t(from)` in seconds. Absent = unbounded.
            Must fit in 32 bits and be `>= min_s` (HTTP 400 otherwise).
          maximum: 2147483647
          minimum: -2147483648
        min_s:
          type:
            - integer
            - 'null'
          format: int64
          description: |-
            Lower bound on `t(to) − t(from)` in seconds. Default `0`. Must fit
            in 32 bits.
          default: 0
          maximum: 2147483647
          minimum: -2147483648
        to:
          $ref: '#/components/schemas/TimeLagMember'
          description: The bounded end.
        type:
          type: string
          description: 'Discriminator: always `time_lag`.'
          enum:
            - time_lag
      additionalProperties: false
    RelationCompatibleVehicles:
      type: object
      title: Compatible vehicles
      description: |-
        Which vehicles may serve a set of tasks together. Every two distinct
        vehicles serving `stops` must be compatible under
        `vehicles[].compatible_with`; with no lists it restricts nothing.
        Tasks left unassigned impose nothing, so a group may be served
        partially. Hard.

        Every task must also be named by a `synchronized` or `time_lag`
        relation; a task named by neither is rejected with HTTP 400. At most
        16 tasks per relation.
      required:
        - stops
        - type
      properties:
        stops:
          type: array
          items:
            type: string
          description: |-
            Job ids, 2 to 16. A shipment's own id or a shipment-leg stop id
            is rejected with HTTP 400.
          example:
            - job-1
            - job-2
          maxItems: 16
          minItems: 2
        type:
          type: string
          description: 'Discriminator: always `compatible_vehicles`.'
          enum:
            - compatible_vehicles
      additionalProperties: false
    RelationAllOrNone:
      type: object
      title: All or none
      description: |-
        All or none: either every member in `stops` is served, or none is.
        Sets no timing; combine with `synchronized` or `time_lag` for when.
        Hard. Overlapping `all_or_none` relations chain: one on A and B plus
        one on B and C serve all three together or none of them. A group with
        a task in a vehicle's locked prefix (`plan.routes[].locked_count`) is
        exempt. A member that could be served but is held back because
        another cannot reports `all_or_none` in `unassigned[].reasons`.

        Every task must also be named by a `synchronized` or `time_lag`
        relation; a task named by neither is rejected with HTTP 400.
      required:
        - stops
        - type
      properties:
        stops:
          type: array
          items:
            type: string
          description: |-
            Job ids, at least two. A shipment's own id or a shipment-leg stop id
            is rejected with HTTP 400.
          example:
            - job-1
            - job-2
          minItems: 2
        type:
          type: string
          description: 'Discriminator: always `all_or_none`.'
          enum:
            - all_or_none
      additionalProperties: false
    ShipmentStop:
      type: object
      description: One leg of a shipment (pickup or delivery).
      required:
        - location
      properties:
        id:
          type:
            - string
            - 'null'
          description: >-
            Stop id: 1 to 128 characters when set. Defaults to

            `"{shipment}:pickup"` / `"{shipment}:delivery"`. The effective id

            (custom or default) must be unique across all job, shipment, and
            stop

            ids — a collision is rejected with HTTP 400.
          example: ship-1:pickup
          maxLength: 128
          minLength: 1
        location:
          $ref: '#/components/schemas/Place'
          description: Where this stop happens.
        service_duration_s:
          type:
            - integer
            - 'null'
          format: int32
          description: |-
            On-site service time paid per job (seconds).

            This is the nominal figure. The serving vehicle's `service_factor`
            scales it — the time actually scheduled is
            `service_duration_s / service_factor` — so the solution's
            `stops[].service_s` may differ from what you sent here. Default `0`;
            a negative value is rejected with HTTP 400.
          example: 300
          minimum: 0
        setup_duration_s:
          type:
            - integer
            - 'null'
          format: int32
          description: |-
            Setup time paid once on arrival at a new location (seconds),
            distinct from `service_duration_s`. Skipped when the immediate real
            predecessor in the route already sits at this location — co-located
            stops share one setup.

            Not scaled by the vehicle's `service_factor`: setup is fixed arrival
            overhead (parking, walking to the door), not the work itself.
            Default `0`; a negative value is rejected with HTTP 400.
          example: 60
          minimum: 0
        time_windows:
          type:
            - array
            - 'null'
          items:
            $ref: '#/components/schemas/TimeWindow'
          description: Time windows for this stop.
      additionalProperties: false
      example:
        id: ship-1:pickup
        location:
          - 3.725
          - 51.05
        service_duration_s: 300
        setup_duration_s: 60
        time_windows:
          - from: '2026-07-01T09:00:00+02:00'
            to: '2026-07-01T12:00:00+02:00'
    MatrixEngine:
      type: string
      description: >-
        Which data prices travel times and distances. Omitted, it follows

        `travel.departure_time`: `osm` without one, `tomtom` with one.


        - `tomtom`: TomTom historical-typical traffic at the departure time, or
        at
          the moment of the request without one.
        - `osm`: OpenStreetMap free-flow times. No traffic, so
          `travel.departure_time` is rejected with 400.
        - `tomtom_real_time`: TomTom live traffic at the moment of the request.
          **Not yet enabled: currently always rejected with 403 `feature_not_enabled`.** Once enabled:
          `travel.departure_time` is rejected; every vehicle must use the `car`
          profile; at most 100 distinct locations; up to ~25 s of matrix fetch at
          100 locations.
        - `straight_line`: great-circle distance at `travel.speed_kmh`, no road
          network. `travel.departure_time`, `options.polyline` and any vehicle
          `profile` other than `car` are rejected with 400.
      enum:
        - tomtom
        - osm
        - tomtom_real_time
        - straight_line
      example: osm
    VehicleLimits:
      type: object
      description: Vehicle distance/time limits.
      properties:
        max_distance_m:
          type:
            - integer
            - 'null'
          format: int64
          description: |-
            Hard maximum distance in metres. `0` to `2147483647`; a value
            outside that range is rejected with HTTP 400 `out_of_range`.
          example: 200000
          maximum: 2147483647
          minimum: 0
        max_leg_travel_s:
          type:
            - integer
            - 'null'
          format: int64
          description: >-
            Hard cap in seconds on any single travel leg of this vehicle's
            routes:

            the `travel_s` reported on each stop, start-to-first-task and

            last-task-to-`end` included. Travel only: setup, service and wait do

            not count, and `speed_factor` applies first. A leg made free by

            `free_start_travel`/`free_end_travel`, or absent on an open start or

            end, is exempt. Absent = no cap. Must be > 0: `0` would make every

            leg too long rather than mean "no cap", so it is rejected with HTTP

            400 `out_of_range`, as is a value that does not fit in 32 bits. A

            breach is reported as `max_leg_travel`.
          example: 3600
          maximum: 2147483647
          minimum: 1
        max_route_duration_s:
          type:
            - integer
            - 'null'
          format: int64
          description: |-
            Hard maximum route duration in seconds: the whole-route span —
            travel, service, and wait all count, depot load and unload included.
            The same quantity as the response's `routes[].time.span_s`. `0` to
            `2147483647`; a value outside that range is rejected with HTTP 400
            `out_of_range`.
          example: 32400
          maximum: 2147483647
          minimum: 0
        max_stops:
          type:
            - integer
            - 'null'
          format: int32
          description: >-
            Maximum stops served per shift; a job counts as one stop.

            Start, end, break and reload stops in `routes[].stops` do not count;
            a

            shipment counts as two stops.
          example: 50
          minimum: 0
        periods:
          type: array
          items:
            $ref: '#/components/schemas/PeriodLimit'
          description: >-
            Hard caps on this vehicle's totals across all of its shifts in a
            day,

            a week or an explicit window. Each shift counts toward the one

            period that contains its `from`, read in that shift's own UTC
            offset.

            A recurring period with none of the vehicle's shifts is skipped; an

            explicit window with no shift start is a 400.
      additionalProperties: false
      example:
        max_distance_m: 200000
        max_route_duration_s: 32400
    TagPreference:
      type: object
      description: A soft tag preference, priced.
      required:
        - tag
        - violation_cost
      properties:
        tag:
          type: string
          description: Preferred tag.
          example: north
        violation_cost:
          type: number
          format: double
          description: >-
            Cost, in the objective's currency, incurred when a task without this

            tag is served. Must be finite and `>= 0`, and one vehicle's entries

            must sum to at most 21474.83647 (a task lacking every tag pays them
            all).
          example: 25
          maximum: 21474.83647
          minimum: 0
      additionalProperties: false
      example:
        tag: north
        violation_cost: 25
    Profile:
      type: string
      description: A vehicle's routing profile.
      enum:
        - car
        - truck
        - bike
      example: car
    Shift:
      type: object
      description: One working shift for a vehicle.
      required:
        - id
        - from
        - to
      properties:
        breaks:
          type:
            - array
            - 'null'
          items:
            $ref: '#/components/schemas/Break'
          description: >-
            Scheduled driver breaks within this shift — see the `Break` schema
            for

            the supported forms. Injected breaks appear as `break` stops in the

            response routes.
        capacity:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/Quantities'
              description: >-
                Capacity for this shift only. Each dimension named here replaces
                the

                vehicle's value for that dimension; dimensions not named keep
                the

                vehicle's `capacity`.
          description: |-
            Capacity for this shift only. Each dimension named here replaces the
            vehicle's value for that dimension; dimensions not named keep the
            vehicle's `capacity`.
        costs:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/ShiftCosts'
              description: >-
                Costs for this shift only. Each field set here replaces the same

                field of the vehicle's `costs` (or, when the vehicle does not
                set it,

                of `objective.costs`) for this shift; fields not set keep that
                value.

                `per_used_vehicle` is not accepted here: it prices the vehicle,
                not a

                shift.
          description: >-
            Costs for this shift only. Each field set here replaces the same

            field of the vehicle's `costs` (or, when the vehicle does not set
            it,

            of `objective.costs`) for this shift; fields not set keep that
            value.

            `per_used_vehicle` is not accepted here: it prices the vehicle, not
            a

            shift.
        end:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/ShiftPlace'
              description: Where the shift ends. Absent = open end.
          description: Where the shift ends. Absent = open end.
        free_end_travel:
          type:
            - boolean
            - 'null'
          description: |-
            Symmetric to `free_start_travel` for travel from the last task to
            `end`; `true` without `end` is a 400.
          example: false
        free_start_travel:
          type:
            - boolean
            - 'null'
          description: |-
            When `true`, travel from `start` to the first task is free: it takes
            no time and costs nothing, and is left out of the route's distance
            and duration. The depot is still reported (a real stop, snapped
            coordinate, departure time). Distinct from omitting `start` (an open
            route, which drops the depot entirely). `true` without `start` is
            rejected with HTTP 400 `conflicting_fields`. Default `false`.
          example: false
        from:
          type: string
          format: date-time
          description: Shift start (RFC 3339 instant, UTC offset required).
          example: '2026-07-01T08:00:00+02:00'
        id:
          type: string
          description: 'Shift id: 1 to 128 characters, unique within this vehicle.'
          example: mon
          maxLength: 128
          minLength: 1
        initial_load:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/Quantities'
              description: >-
                Load already on the vehicle at shift start (pre-loaded), per

                dimension. Occupies capacity on the first trip only — flushed at
                the

                first reload — so it reduces first-trip room per dimension,
                enforced

                by the per-trip capacity constraint. Not validated against

                `capacity` at request time: a value at or beyond capacity is

                accepted and surfaces as a solve-time result (jobs left
                unassigned,

                or deferred until the vehicle's first reload flushes it), not an

                HTTP 400. A dimension named here but absent from `capacity`
                behaves

                the same as capacity `0` in that dimension.
          description: |-
            Load already on the vehicle at shift start (pre-loaded), per
            dimension. Occupies capacity on the first trip only — flushed at the
            first reload — so it reduces first-trip room per dimension, enforced
            by the per-trip capacity constraint. Not validated against
            `capacity` at request time: a value at or beyond capacity is
            accepted and surfaces as a solve-time result (jobs left unassigned,
            or deferred until the vehicle's first reload flushes it), not an
            HTTP 400. A dimension named here but absent from `capacity` behaves
            the same as capacity `0` in that dimension.
        limits:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/ShiftLimits'
              description: |-
                Limits for this shift only. Each field set here replaces the
                vehicle's `limits` value; fields not set keep it.
          description: |-
            Limits for this shift only. Each field set here replaces the
            vehicle's `limits` value; fields not set keep it.
        max_overtime_s:
          type:
            - integer
            - 'null'
          format: int32
          description: |-
            Maximum allowed overtime beyond `to` in seconds. Overtime is reached
            by working late, never by waiting: a `synchronized` or `time_lag`
            hold stops at `to`. Default: none (`to` is hard). Must be `>= 0`.
          example: 1800
          minimum: 0
        max_trips:
          type:
            - integer
            - 'null'
          format: int32
          description: >-
            Maximum trips (depot legs) in this shift, across all reload depots.

            Counts trips, not reloads: `trips = reloads_used + 1`. `max_trips:
            1`

            means a single trip — reloads are permitted in `reloads` but the cap

            prevents ever using one. `reloads` present with `max_trips` absent

            means unlimited trips. A single reload depot can only be

            revisited up to 8 times per shift regardless of `max_trips`;

            a `max_trips` beyond that against one depot

            is accepted but the extra headroom is unreachable — list more than

            one reload depot to raise the practical ceiling. `0` is rejected
            with

            HTTP 400 (the first trip already counts as trip 1).
          example: 2
          minimum: 1
        reloads:
          type:
            - array
            - 'null'
          items:
            type: string
          description: >-
            Depots (by `depots[].id`, each at most once) the vehicle may return
            to

            mid-shift to reload and start a fresh trip. Each visit takes that

            depot's `service_duration_s` and `per_reload`. Cap trips with

            `max_trips`.
        serves_tags:
          type:
            - array
            - 'null'
          items:
            type: string
          description: >-
            Hard tag scope: when set (non-empty), this shift

            serves only tasks carrying at least one of these tags. Untagged
            tasks

            remain serveable by every shift. Absent or empty = serves
            everything.
        service_factor:
          type:
            - number
            - 'null'
          format: double
          description: >-
            Work-rate factor for this shift only. It REPLACES

            `vehicles[].service_factor` for the shift when set (absent = inherit
            the

            vehicle's value, which itself defaults to 1.0); a task's

            `service_factors[].factor` for this vehicle multiplies it.


            Same meaning, direction, and `[0.001, 5]` range as the vehicle-level

            field: service time is DIVIDED by it, effective service time =

            `service_duration_s / service_factor`, so above `1` is faster. Use
            it

            when the same driver works at a different rate on different days — a

            measured per-day efficiency, a trainee shift, a two-person crew on

            Fridays — instead of collapsing those days into one vehicle-level
            mean.


            Scope is identical to the vehicle-level field: task

            `service_duration_s` only, never `setup_duration_s`, depot handling

            (`depots[].service_duration_s`, `start`/`end.service_duration_s`),

            waiting, or breaks.
          maximum: 5
          minimum: 0.001
        skills:
          type:
            - array
            - 'null'
          items:
            $ref: '#/components/schemas/VehicleSkill'
          description: >-
            Capabilities this shift provides, replacing the vehicle's `skills`

            for this shift only (a technician covering another region on one

            day, a crew that only carries a certification on some shifts). A

            job's `required_skills` must all appear in the skills of the shift
            that

            serves it, at a level of at least the job's `min_level`. Same entry

            shape as `vehicles[].skills`. Absent = the vehicle's `skills`; `[]`
            =

            this shift provides none.
        start:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/ShiftPlace'
              description: Where the shift starts. Absent = open start.
          description: Where the shift starts. Absent = open start.
        to:
          type: string
          format: date-time
          description: >-
            Shift end (RFC 3339 instant, UTC offset required; hard unless
            `max_overtime_s` set). The

            route must be finished by then, end-depot unload included.
          example: '2026-07-01T17:00:00+02:00'
      additionalProperties: false
      example:
        end:
          location:
            - 3.7174
            - 51.0543
        from: '2026-07-01T08:00:00+02:00'
        id: mon
        start:
          location:
            - 3.7174
            - 51.0543
        to: '2026-07-01T17:00:00+02:00'
    VehicleSkill:
      type: object
      description: |-
        A capability a vehicle or shift provides, at a proficiency level:
        `{ "name": "gas", "level": 3 }`. Always an object; a bare string is
        rejected at parse.
      required:
        - name
      properties:
        level:
          type:
            - integer
            - 'null'
          format: int32
          description: |-
            Proficiency level, `>= 1`; absent = 1. Satisfies every job
            requirement of this skill whose `min_level` is at most this value.
          example: 3
          minimum: 1
        name:
          type: string
          description: Skill name.
          example: gas
      additionalProperties: false
      example:
        level: 3
        name: gas
    PlanStopJob:
      type: object
      title: PlanStopJob
      description: A job, by its id.
      required:
        - id
        - type
      properties:
        id:
          type: string
          description: Job id.
          example: boiler-17
        type:
          type: string
          description: 'Discriminator: always `job`.'
          enum:
            - job
      additionalProperties: false
    PlanStopPickup:
      type: object
      title: PlanStopPickup
      description: A shipment's pickup leg.
      required:
        - id
        - type
      properties:
        id:
          type: string
          description: 'The leg''s stop id: its own `id`, else `{shipment}:pickup`.'
          example: parts-9:pickup
        type:
          type: string
          description: 'Discriminator: always `pickup`.'
          enum:
            - pickup
      additionalProperties: false
    PlanStopDelivery:
      type: object
      title: PlanStopDelivery
      description: A shipment's delivery leg.
      required:
        - id
        - type
      properties:
        id:
          type: string
          description: 'The leg''s stop id: its own `id`, else `{shipment}:delivery`.'
          example: parts-9:delivery
        type:
          type: string
          description: 'Discriminator: always `delivery`.'
          enum:
            - delivery
      additionalProperties: false
    PlanStopReload:
      type: object
      title: PlanStopReload
      description: 'A reload at a depot: the next trip starts there.'
      required:
        - depot
        - type
      properties:
        depot:
          type: string
          description: A depot id from the shift's `reloads`.
          example: gent
        type:
          type: string
          description: 'Discriminator: always `reload`.'
          enum:
            - reload
      additionalProperties: false
    TargetInstant:
      type: string
      description: |-
        Which instant of a task a `target` is measured at, named after the
        response stop field that reports it.
      enum:
        - arrival
        - departure
    GroupingDistance:
      type: string
      description: How neighbour distance is measured for `objective.grouping`.
      enum:
        - straight_line
        - road
      example: straight_line
    RelationInstant:
      type: string
      description: >-
        Which instant of a member a `synchronized` or `time_lag` relation is

        measured at, named after the response stop field that reports it. There
        is

        no `arrival`: a relation bounds when service happens, not when the
        vehicle

        gets there.
      enum:
        - service_start
        - departure
    TimeLagMember:
      type: object
      description: One end of a `time_lag` relation.
      required:
        - stop
      properties:
        'on':
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/RelationInstant'
              description: >-
                Which instant of the job the bound is measured at:
                `service_start`

                (default) or `departure`, the stop fields of the same names.
          description: |-
            Which instant of the job the bound is measured at: `service_start`
            (default) or `departure`, the stop fields of the same names.
          default: service_start
        stop:
          type: string
          description: |-
            The stop: a job id. A shipment's own id or a shipment-leg stop id is
            rejected with HTTP 400.
          example: job-1
      additionalProperties: false
      example:
        'on': departure
        stop: job-1
    PeriodLimit:
      type: object
      description: >-
        A cap on a vehicle's totals across every shift that starts in one
        period.

        Set exactly one of `every`, or `from` and `to`; and at least one
        maximum.
      properties:
        every:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/PeriodEvery'
              description: >-
                A recurring period: every `day`, or every `week` (weeks start
                Monday).
          description: >-
            A recurring period: every `day`, or every `week` (weeks start
            Monday).
        from:
          type:
            - string
            - 'null'
          format: date-time
          description: >-
            Start of one explicit period (RFC 3339, UTC offset required), with
            `to`. The window must

            hold at least one of the vehicle's shift starts; a period inside one

            shift is not supported.
          example: '2026-10-05T00:00:00+02:00'
        max_distance_m:
          type:
            - integer
            - 'null'
          format: int64
          description: Hard cap on the summed distance driven, in metres. Must be `>= 0`.
          example: 900000
          minimum: 0
        max_route_duration_s:
          type:
            - integer
            - 'null'
          format: int64
          description: >-
            Hard cap on the summed route span of the period's shifts, measured
            as a

            shift's `max_route_duration_s` is, in seconds. An unused shift adds

            nothing. Must be `>= 0`.
          example: 144000
          minimum: 0
        max_stops:
          type:
            - integer
            - 'null'
          format: int32
          description: >-
            Hard cap on served stops across the period's shifts; a job counts as

            one stop. Start, end, break and reload stops in `routes[].stops` do
            not

            count; a shipment counts as two stops.
          example: 40
          minimum: 0
        to:
          type:
            - string
            - 'null'
          format: date-time
          description: >-
            End of one explicit period (RFC 3339, UTC offset required,
            exclusive), with `from`.
          example: '2026-10-12T00:00:00+02:00'
      additionalProperties: false
      example:
        every: week
        max_route_duration_s: 144000
        max_stops: 40
      oneOf:
        - not:
            anyOf:
              - properties:
                  every:
                    type: 'null'
                required:
                  - every
              - not:
                  properties:
                    from:
                      type: 'null'
                  required:
                    - from
                required:
                  - from
              - not:
                  properties:
                    to:
                      type: 'null'
                  required:
                    - to
                required:
                  - to
          required:
            - every
        - not:
            anyOf:
              - properties:
                  from:
                    type: 'null'
                required:
                  - from
              - properties:
                  to:
                    type: 'null'
                required:
                  - to
              - not:
                  properties:
                    every:
                      type: 'null'
                  required:
                    - every
                required:
                  - every
          required:
            - from
            - to
    Break:
      oneOf:
        - $ref: '#/components/schemas/BreakWindowed'
        - $ref: '#/components/schemas/BreakTriggered'
        - $ref: '#/components/schemas/BreakFixed'
      description: >-
        A scheduled break within a shift. Tagged by `type`:


        - `windowed`: a break of `duration_s` that starts inside its window.

        - `triggered`: a break of `duration_s` taken once drive or duty time
          passes `threshold_s` (the EU-561 mechanism).
        - `fixed`: a hard off-duty interval from `from` to `to`.


        Breaks are injected between stops and appear in the response as `break`

        stops, carrying the break's `id` when it has one.


        A `triggered` break always resets its accumulator after a qualifying
        break

        and is taken wherever the vehicle is: `resets_accumulator` and
        `location`

        do not exist on it. A `windowed` break takes exactly one `time_window`
        and

        is also taken wherever the vehicle is: a `location` on it is HTTP 400

        `not_supported`.


        There is no paid/unpaid cost accounting: a break's duration is never

        separately priced, and no `paid` field exists on this schema — sending
        one

        is rejected as an unknown field (HTTP 400, `unknown_field`), like any
        other extra key.
      example:
        after: drive
        duration_s: 1800
        threshold_s: 14400
        type: triggered
      discriminator:
        propertyName: type
        mapping:
          fixed: '#/components/schemas/BreakFixed'
          triggered: '#/components/schemas/BreakTriggered'
          windowed: '#/components/schemas/BreakWindowed'
    ShiftCosts:
      type: object
      description: >-
        The `Costs` fields for one shift, minus `per_used_vehicle`. A shift is
        one

        of a vehicle's days, so a charge for using the vehicle at all is set on
        the

        vehicle (or the fleet), where it applies once across its shifts. A field

        set here replaces the vehicle's value (else the fleet's) for this shift

        only; the `Costs` schema says what each field prices.
      properties:
        per_overtime_hour:
          type:
            - number
            - 'null'
          format: double
          description: |-
            Per hour this route finishes past the shift's `to`, as
            `Costs.per_overtime_hour`.
          example: 0
          minimum: 0
        per_route:
          type:
            - number
            - 'null'
          format: double
          description: Flat charge for this used shift, as `Costs.per_route`.
          example: 50
          maximum: 1000000000
          minimum: 0
        per_route_hour:
          type:
            - number
            - 'null'
          format: double
          description: Per hour of this route's wall-clock span, as `Costs.per_route_hour`.
          example: 35
          minimum: 0
        per_travel_km:
          type:
            - number
            - 'null'
          format: double
          description: Per kilometre driven on this shift, as `Costs.per_travel_km`.
          example: 0.2
          minimum: 0
        per_wait_hour:
          type:
            - number
            - 'null'
          format: double
          description: |-
            Per hour spent waiting for a time window to open on this shift, as
            `Costs.per_wait_hour`.
          example: 0
          minimum: 0
      additionalProperties: false
      example:
        per_route: 50
        per_travel_km: 0.3
    ShiftPlace:
      type: object
      description: 'Where a shift starts or ends: exactly one of `location` or `depot`.'
      properties:
        depot:
          type:
            - string
            - 'null'
          description: A `depots[].id`. Excludes `location`.
          example: gent
        location:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/Place'
              description: An inline place. Excludes `depot`.
          description: An inline place. Excludes `depot`.
        service_duration_s:
          type:
            - integer
            - 'null'
          format: int32
          description: >-
            Handling time in seconds at this end: the morning load at `start`,
            the

            evening unload at `end`. Overrides the depot's `service_duration_s`;

            default: the depot's value, or 0 for a `location` end. Not applied
            on

            free travel (`free_start_travel`/`free_end_travel`). Not scaled by

            `service_factor`.


            The unload must finish by the shift's `to` (or `to +
            max_overtime_s`):

            a route whose unload would run past it is infeasible. A shift whose

            load and unload alone exceed it is rejected with HTTP 400.
          example: 600
          minimum: 0
      additionalProperties: false
      example:
        depot: gent
      oneOf:
        - not:
            anyOf:
              - properties:
                  location:
                    type: 'null'
                required:
                  - location
          required:
            - location
        - not:
            anyOf:
              - properties:
                  depot:
                    type: 'null'
                required:
                  - depot
          required:
            - depot
    ShiftLimits:
      type: object
      description: |-
        Per-shift limits: the route-level `limits` fields a shift may override.
        Period limits apply across shifts, so they stay on the vehicle.
      properties:
        max_distance_m:
          type:
            - integer
            - 'null'
          format: int64
          description: |-
            Hard cap on this shift's route distance, in metres. `0` to
            `2147483647`, as on the vehicle.
          example: 200000
          maximum: 2147483647
          minimum: 0
        max_route_duration_s:
          type:
            - integer
            - 'null'
          format: int64
          description: |-
            Hard cap on this shift's route span, in seconds. `0` to
            `2147483647`, as on the vehicle.
          example: 32400
          maximum: 2147483647
          minimum: 0
        max_stops:
          type:
            - integer
            - 'null'
          format: int32
          description: >-
            Maximum stops served in this shift; a job counts as one stop.

            Start, end, break and reload stops in `routes[].stops` do not count;
            a

            shipment counts as two stops.
          example: 50
          minimum: 0
      additionalProperties: false
      example:
        max_stops: 12
    PeriodEvery:
      type: string
      description: The recurrence of a period limit (`limits.periods[].every`).
      enum:
        - day
        - week
    BreakWindowed:
      type: object
      title: Windowed
      description: A break that must start inside a time window.
      required:
        - duration_s
        - time_window
        - type
      properties:
        duration_s:
          type: integer
          format: int32
          description: Break duration in seconds. Must be `> 0`.
          minimum: 1
        id:
          type:
            - string
            - 'null'
          description: |-
            Optional break id: 1 to 128 characters, unique within the shift.
            Echoed as `id` on the `break` stop that realises this break.
          example: lunch
          maxLength: 128
          minLength: 1
        location:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/Place'
              description: >-
                Not supported on V3: a windowed break is taken wherever the

                vehicle is when it starts, so a `location` here is rejected with

                HTTP 400 `not_supported`. Set only by the V2 compatibility
                mapper.
          description: |-
            Not supported on V3: a windowed break is taken wherever the
            vehicle is when it starts, so a `location` here is rejected with
            HTTP 400 `not_supported`. Set only by the V2 compatibility mapper.
        time_window:
          $ref: '#/components/schemas/BreakTimeWindow'
          description: The window bounding the break's **start**.
        type:
          type: string
          description: 'Discriminator: always `windowed`.'
          enum:
            - windowed
      additionalProperties: false
    BreakTriggered:
      type: object
      title: Triggered
      description: A break taken once drive or duty time passes a threshold.
      required:
        - duration_s
        - after
        - threshold_s
        - type
      properties:
        after:
          $ref: '#/components/schemas/TriggerAfter'
          description: 'What to accumulate: drive time or duty time.'
        duration_s:
          type: integer
          format: int32
          description: Break duration in seconds. Must be `> 0`.
          minimum: 1
        id:
          type:
            - string
            - 'null'
          description: |-
            Optional break id: 1 to 128 characters, unique within the shift.
            Echoed as `id` on the `break` stop that realises this break.
          example: lunch
          maxLength: 128
          minLength: 1
        threshold_s:
          type: integer
          format: int64
          description: |-
            Trigger threshold in seconds (strict `>` — the break fires when the
            accumulator exceeds this). EU 561/2006 drive rule: `16200` (4 h 30).
            Must be `> 0` and fit in 32 bits.
          example: 14400
          maximum: 2147483647
          minimum: 1
        type:
          type: string
          description: 'Discriminator: always `triggered`.'
          enum:
            - triggered
      additionalProperties: false
    BreakFixed:
      type: object
      title: Fixed
      description: |-
        Mandatory off-duty interval at a fixed time. No service is scheduled
        inside it, but a drive already under way at `from` continues through
        it and counts in both `travel_s` and `break_s`.
      required:
        - from
        - to
        - type
      properties:
        from:
          type: string
          format: date-time
          description: Start of the break (RFC 3339 instant, UTC offset required).
        id:
          type:
            - string
            - 'null'
          description: |-
            Optional break id: 1 to 128 characters, unique within the shift.
            Echoed as `id` on the `break` stop that realises this break.
          example: lunch
          maxLength: 128
          minLength: 1
        to:
          type: string
          format: date-time
          description: End of the break (RFC 3339 instant, UTC offset required).
        type:
          type: string
          description: 'Discriminator: always `fixed`.'
          enum:
            - fixed
      additionalProperties: false
    BreakTimeWindow:
      type: object
      description: The window a `windowed` break must start in. Always hard.
      required:
        - from
        - to
      properties:
        from:
          type: string
          format: date-time
          description: Earliest break start (RFC 3339 instant, UTC offset required).
        to:
          type: string
          format: date-time
          description: Latest break start (RFC 3339 instant, UTC offset required).
      additionalProperties: false
      example:
        from: '2026-07-01T12:00:00+02:00'
        to: '2026-07-01T13:30:00+02:00'
    TriggerAfter:
      type: string
      description: |-
        What a break trigger accumulates since the last break: `drive` counts
        driving time only; `duty` counts travel, service and setup time, but not
        waiting (for a time window to open or for a coupled task).
      enum:
        - drive
        - duty
      example: drive
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: Authorization

````

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