openapi: 3.1.0
info:
  title: Scheduled Routes Optimization API
  version: "1.2.0"
  summary: Fleet routing and scheduling optimization, powered by the Advanced Travel Oracle engine.
  description: |
    Submit a routing and scheduling **problem** (fleet + jobs + constraints) and get back an
    optimized **solution** (routes, stops, ETAs, and statistics). Supports pickups, deliveries,
    pickup-and-delivery (PDPTW), time windows, skills, capacities, and live re-optimization.

    The API is **asynchronous**: `POST` a problem, receive an `id`, then `GET` that id until the
    solution is ready. See *How optimization works* in the docs for the poll pattern.

    > **Trying it out here:** click **Authorize**, paste **your own** API key, then use **Try it out**
    > on an endpoint. Calls go to the production server against your account. Never paste a key that
    > isn't yours, and don't run confidential data through the in-browser console.
  contact:
    name: DDS Wireless — Scheduled Routes
    url: https://ddswireless.com/scheduled-routes-api-documentation/
  x-logo:
    url: ../assets/sr-logo.png

servers:
  - url: https://iq.scheduledroutes.ddswireless.net
    description: Production

security:
  - bearerAuth: []

tags:
  - name: Optimization
    description: Submit a problem and retrieve its optimized solution.

paths:
  /raas/optimization:
    post:
      tags: [Optimization]
      operationId: submitOptimization
      summary: Submit an optimization problem
      description: |
        Submits a routing/scheduling problem for optimization. Returns quickly with an `id`;
        the solve happens in the background. Poll `GET /raas/optimization/{id}` to retrieve the result.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OptimizationRequest'
            examples:
              helloWorld:
                summary: Hello-world (2 vehicles, 3 jobs, minimize travel time)
                value:
                  problem:
                    fleet:
                      - id: 1
                        shifts:
                          - start:
                              time: "2026-07-31T08:00:00-07:00"
                              location: [49.28270, -123.11540]
                            end:
                              time: "2026-07-31T17:00:00-07:00"
                              location: [49.28270, -123.11540]
                        capacities:
                          - name: "parcels"
                            units: 40
                      - id: 2
                        shifts:
                          - start:
                              time: "2026-07-31T08:00:00-07:00"
                              location: [49.28270, -123.11540]
                            end:
                              time: "2026-07-31T17:00:00-07:00"
                              location: [49.28270, -123.11540]
                        capacities:
                          - name: "parcels"
                            units: 40
                    jobs:
                      - id: 101
                        deliveries:
                          - id: 1001
                            location: [49.26340, -123.13830]
                            duration: 300
                            demand: [2]
                      - id: 102
                        deliveries:
                          - id: 1002
                            location: [49.24660, -123.06340]
                            duration: 300
                            demand: [1]
                      - id: 103
                        deliveries:
                          - id: 1003
                            location: [49.28150, -123.12070]
                            duration: 300
                            demand: [3]
                  objective: 1
                  configuration:
                    units: "metric"
                    statistics: true
                    unassignedTasks: true
                    polylineType: "none"
      responses:
        '202':
          description: Accepted for processing. Poll the returned `id` to get the solution.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubmitResponse'
              example:
                id: "b1f2c3d4-5e6f-7890-abcd-ef1234567890"
                status: "accepted"
                message: "Optimization request accepted."
                error: null
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'

  /raas/optimization/{id}:
    get:
      tags: [Optimization]
      operationId: getOptimization
      summary: Retrieve an optimization result
      description: |
        Retrieves the solution for a previously submitted problem. Returns `202` while the solve
        is still running and `200` with the full `solution` once it is complete.
      parameters:
        - name: id
          in: path
          required: true
          description: The `id` returned by `POST /raas/optimization`.
          schema:
            type: string
          example: "b1f2c3d4-5e6f-7890-abcd-ef1234567890"
      responses:
        '200':
          description: Solution ready.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SolutionResponse'
        '202':
          description: Still processing — poll again shortly.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubmitResponse'
              example:
                id: "b1f2c3d4-5e6f-7890-abcd-ef1234567890"
                status: "processing"
                message: "Optimization in progress."
                error: null
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/ServerError'

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: "Send your key as `Authorization: Bearer <API_KEY>`."

  responses:
    BadRequest:
      description: Input validation failed (malformed payload, missing required field, or infeasible request).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Unauthorized:
      description: Missing or invalid API key.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Forbidden:
      description: The key is not permitted to perform this action.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    NotFound:
      description: Path or resource not found.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    RateLimited:
      description: Rate limit exceeded — slow down and retry with backoff.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    ServerError:
      description: Internal service error.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'

  schemas:
    OptimizationRequest:
      type: object
      required: [problem]
      properties:
        problem:
          $ref: '#/components/schemas/Problem'
        objective:
          $ref: '#/components/schemas/Objective'
        configuration:
          $ref: '#/components/schemas/Configuration'
        version:
          type: string
          description: Optional API/contract version tag.
          example: "1.2"

    Problem:
      type: object
      required: [fleet, jobs]
      description: The routing/scheduling problem — at least one vehicle and one job.
      properties:
        fleet:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/Vehicle'
        jobs:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/Job'

    Vehicle:
      type: object
      required: [id, capacities]
      properties:
        id:
          type: integer
          description: Unique vehicle id.
        shifts:
          type: array
          items:
            $ref: '#/components/schemas/Shift'
        capacities:
          type: array
          description: Named capacity buckets, e.g. parcels / weight / seats.
          items:
            $ref: '#/components/schemas/Capacity'
        skills:
          type: array
          items:
            type: string
          description: Skills this vehicle/driver provides (matched against job skills).
        limits:
          $ref: '#/components/schemas/VehicleLimits'
        lastKnownLocation:
          $ref: '#/components/schemas/Location'
        routeTemplateId:
          type: integer
          description: Optional route template to apply (v1.1+).
        maxInstances:
          type: integer
          description: Max number of route instances this vehicle may run.

    Shift:
      type: object
      required: [start, end]
      properties:
        start:
          $ref: '#/components/schemas/ShiftEndpoint'
        end:
          $ref: '#/components/schemas/ShiftEndpoint'
        breaks:
          type: array
          items:
            $ref: '#/components/schemas/Break'

    ShiftEndpoint:
      type: object
      required: [time, location]
      properties:
        time:
          type: string
          format: date-time
          description: ISO 8601 with timezone offset.
          example: "2026-07-31T08:00:00-07:00"
        location:
          $ref: '#/components/schemas/Location'

    Break:
      type: object
      properties:
        serviceWindow:
          $ref: '#/components/schemas/ServiceWindow'
        duration:
          type: integer
          description: Break length in seconds.
        location:
          $ref: '#/components/schemas/Location'

    Capacity:
      type: object
      required: [name, units]
      properties:
        name:
          type: string
          example: "parcels"
        units:
          type: integer
          example: 40

    VehicleLimits:
      type: object
      properties:
        maxDistance:
          type: number
          description: Max route distance (in the request's units).
        maxStops:
          type: integer
        lifoDepth:
          type: integer
          description: Depth for last-in-first-out load constraints.

    Job:
      type: object
      required: [id]
      description: |
        A unit of work. Define `pickups`, `deliveries`, or both. A job may not have
        multiple pickups **and** multiple deliveries at the same time.
      properties:
        id:
          type: integer
        pickups:
          type: array
          items:
            $ref: '#/components/schemas/Task'
        deliveries:
          type: array
          items:
            $ref: '#/components/schemas/Task'

    Task:
      type: object
      required: [id, location]
      properties:
        id:
          type: integer
        location:
          $ref: '#/components/schemas/Location'
        serviceWindows:
          type: array
          items:
            $ref: '#/components/schemas/ServiceWindow'
        duration:
          type: integer
          description: Service/dwell time at the stop, in seconds.
        demand:
          type: array
          items:
            type: integer
          description: Capacity consumed, aligned to the vehicle `capacities` order.
        skills:
          type: array
          items:
            type: string
        status:
          type: string
          description: Re-optimization only — current state of the task.
          enum: [pending, in_progress, performed]
        vehicleId:
          type: integer
          description: Re-optimization only — vehicle currently assigned.
        eta:
          type: string
          format: date-time
          description: Re-optimization only — current estimated time of arrival.

    Location:
      description: |
        Either an array `[latitude, longitude]` or an object `{lat, lng}`.
        Use at least 5 decimal places.
      oneOf:
        - type: array
          minItems: 2
          maxItems: 2
          items:
            type: number
          example: [49.28270, -123.11540]
        - type: object
          required: [lat, lng]
          properties:
            lat:
              type: number
            lng:
              type: number

    ServiceWindow:
      type: object
      description: An allowed time range for a task.
      required: [start, end]
      properties:
        start:
          type: string
          format: date-time
          example: "2026-07-31T09:00:00-07:00"
        end:
          type: string
          format: date-time
          example: "2026-07-31T12:00:00-07:00"

    Objective:
      description: |
        How to optimize. Use a built-in code, or an account-configured strategy name (string).
      oneOf:
        - type: integer
          enum: [1, 2, 3]
          description: "1 = minimize total travel time (default), 2 = minimize number of routes, 3 = balance workload."
        - type: string
          description: An account-configured custom strategy name.
      default: 1

    Configuration:
      type: object
      properties:
        polylineType:
          type: string
          enum: [none, plain, encoded]
          default: none
        unassignedTasks:
          type: boolean
          description: Include unassigned tasks (with reasons) in the solution.
        statistics:
          type: boolean
          description: Include summary statistics in the solution.
        units:
          type: string
          enum: [metric, imperial]
          default: metric
        maxRideTable:
          type: array
          items:
            type: object
          description: Max ride-time bands. Must not overlap or leave gaps.
        enforceLockedTasks:
          type: string
          enum: [full, order, vehicle]
        vehicleCapacities:
          type: object
        freezeWindowLength:
          type: integer
          description: Re-optimization — minutes at the head of the schedule to keep frozen.
        lifoConstrainedCapacities:
          type: array
          items:
            type: string

    SubmitResponse:
      type: object
      properties:
        id:
          type: string
        status:
          type: string
        message:
          type: string
        error:
          type: [string, "null"]

    SolutionResponse:
      type: object
      properties:
        status:
          type: string
        message:
          type: string
        error:
          type: [string, "null"]
        solution:
          $ref: '#/components/schemas/Solution'

    Solution:
      type: object
      properties:
        statistics:
          $ref: '#/components/schemas/Statistics'
        routes:
          type: array
          items:
            $ref: '#/components/schemas/Route'
        unassigned:
          type: array
          items:
            $ref: '#/components/schemas/Unassigned'

    Statistics:
      type: object
      properties:
        distance:
          type: object
          properties:
            totalDistance:
              type: number
            revenueDistance:
              type: number
        vehicles:
          type: object
          properties:
            used:
              type: integer
            unused:
              type: integer
        jobs:
          type: object
          properties:
            scheduledTasks:
              type: integer
            unassignedTasks:
              type: integer
        times:
          type: object
          properties:
            totalHours:
              type: number
            revenueHours:
              type: number

    Route:
      type: object
      properties:
        vehicleId:
          type: integer
        shifts:
          type: array
          items:
            $ref: '#/components/schemas/RouteShift'

    RouteShift:
      type: object
      properties:
        index:
          type: integer
        stops:
          type: array
          items:
            $ref: '#/components/schemas/Stop'

    Stop:
      type: object
      properties:
        ordinal:
          type: integer
        jobId:
          type: integer
        taskId:
          type: integer
        type:
          type: string
          enum: [pickup, delivery, break]
        location:
          $ref: '#/components/schemas/Location'
        eta:
          type: string
          format: date-time
        timeToNext:
          type: integer
          description: Seconds to the next stop.
        distanceToNext:
          type: number
          description: Distance to the next stop, in the request's units.
        waitTime:
          type: integer
          description: Seconds spent waiting for a service window to open.
        polyline:
          type: string
        break:
          type: object

    Unassigned:
      type: object
      properties:
        id:
          type: object
          properties:
            job:
              type: integer
            task:
              type: integer
        reason:
          type: array
          items:
            type: object
            properties:
              code:
                type: string
              description:
                type: string

    ErrorResponse:
      type: object
      properties:
        status:
          type: string
        message:
          type: string
        error:
          type: string
