openapi: 3.1.0
info:
  title: yardspot
  version: '2026-04-03T00:00:00Z'
servers:
  - url: https://api.yardspot.io
tags:
  - name: asset-locs
    description: YardSpot assets
  - name: activities
    description: YardSpot activities
  - name: api-key
    description: YardSpot api keys
  - name: asset-schemas
    description: YardSpot asset schemas
  - name: asset-types
    description: YardSpot asset types
  - name: geo
    description: YardSpot geo shapes
  - name: journal
    description: YardSpot asset event journal
  - name: location
    description: YardSpot locations
  - name: user
    description: YardSpot users
  - name: shuttles
    description: Shuttle profile management (auto-dispatch)
  - name: dispatch
    description: Dispatch queue management and algorithm control (auto-dispatch)
  - name: capabilities
    description: Capability tag management (auto-dispatch)
  - name: linked-moves
    description: Activity linked move chains (auto-dispatch)
paths:
  /asset-loc/{id}:
    get:
      tags:
        - asset-locs
      summary: Get asset by id
      description: Get asset by id
      operationId: getAssetById
      responses:
        401:
          description: Unauthorized
        404:
          description: Asset not found
        200:
          description: successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AssetSummary'
      security:
        - ApiKeyAuthorizer: [ ]
    delete:
      tags:
        - asset-locs
      summary: Delete asset by id
      description: Delete asset by id
      operationId: deleteAssetById
      responses:
        '204':
          description: successful operation
        '401':
          description: Unauthorized
        '500':
          description: Failed to process asset delete
      security:
        - ApiKeyAuthorizer: [ ]
  /asset-locs:
    get:
      tags:
        - asset-locs
      summary: List of active assets summary
      description: list of active assets summary
      operationId: getAssets
      responses:
        202:
          description: successful operation
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AssetSummary'
        401:
          description: Unauthorized
      security:
        - ApiKeyAuthorizer: [ ]
  /asset-loc:
    post:
      tags:
        - asset-locs
      summary: Create or update an asset
      description: Create or update an asset. Update happens asynchronously
      operationId: upsertAsset
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AssetLocationRequest'
      responses:
        '202':
          description: The request has been accepted for processing, but processing hasn't been completed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyString'
        '401':
          description: Unauthorized
      security:
        - ApiKeyAuthorizer: [ ]
  /activity:
    get:
      tags:
        - activities
      summary: List of activities
      description: List of activities. Does not include expired activities and history
      operationId: getActivities
      responses:
        202:
          description: successful operation
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ActivitySummary'
        401:
          description: Unauthorized
      security:
        - ApiKeyAuthorizer: [ ]
    post:
      tags:
        - activities
      summary: Create an activity
      description: Create an activity
      operationId: createActivity
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Activity'
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ActivityModel'
        401:
          description: Unauthorized
      security:
        - ApiKeyAuthorizer: [ ]
  /activity/{id}:
    get:
      tags:
        - activities
      summary: Get an activity
      description: Get an activity
      operationId: getActivity
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ActivityModel'
        401:
          description: Unauthorized
        404:
          description: Activity not found
      security:
        - ApiKeyAuthorizer: [ ]

    post:
      tags:
        - activities
      summary: Update an activity
      description: Update an activity
      operationId: updateActivity
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ActivityRequest'
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ActivityModel'
        401:
          description: Unauthorized
      security:
        - ApiKeyAuthorizer: [ ]
    delete:
      tags:
        - activities
      summary: Delete an activity
      description: Delete an activity
      operationId: deleteActivity
      responses:
        204:
          description: successful operation
        400:
          description: Invalid parameter
        401:
          description: Unauthorized
      security:
        - ApiKeyAuthorizer: [ ]
  /activities-page:
    get:
      tags:
        - activities
      summary: Get activity paged list
      description: Get activity paged list by specified criteria in search params.
      operationId: getActivityPage
      parameters:
        - $ref: '#/components/parameters/nextToken'
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/queryCriteria'
        - $ref: '#/components/parameters/properties'
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                type: object
                properties:
                  next:
                    type: string
                  items:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
        401:
          description: Unauthorized
      security:
        - ApiKeyAuthorizer: [ ]
  /user:
    get:
      tags:
        - user
      summary: Get user list
      description: Get user list
      operationId: getUsers
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/User'
        401:
          description: Unauthorized
      security:
        - ApiKeyAuthorizer: [ ]
    post:
      tags:
        - user
      summary: Create or update a user
      description: Create or update a user. Requires admin permissions
      operationId: upsertUser
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserRequest'
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        401:
          description: Unauthorized
      security:
        - ApiKeyAuthorizer: [ ]
  /user/{id}:
    get:
      tags:
        - user
      summary: Get current user
      description: Get current user
      operationId: getCurrentUser
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        404:
          description: Not Found
        401:
          description: Unauthorized
      security:
        - ApiKeyAuthorizer: [ ]
    delete:
      tags:
        - user
      summary: Delete a user
      description: Delete a user. Requires admin permissions
      operationId: deleteUser
      responses:
        204:
          description: successful operation
        400:
          description: Invalid Parameter
        401:
          description: Unauthorized
        404:
          description: Not Found
      security:
        - ApiKeyAuthorizer: [ ]
  /geo:
    get:
      tags:
        - geo
      summary: Get map geo shapes
      description: Get map geo shapes
      operationId: getGeo
      responses:
        200:
          description: successful operation, returns GeoJSON
          content:
            application/json:
              schema:
                type: object
        401:
          description: Unauthorized
      security:
        - ApiKeyAuthorizer: [ ]
    post:
      tags:
        - geo
      summary: Update map geo shapes
      description: Update map geo shapes. Requires admin permissions
      operationId: updateGeo
      requestBody:
        required: true
        description: GeoJSON
        content:
          application/json:
            schema:
              type: object
      responses:
        200:
          description: successful operation, return GeoJSON
          content:
            application/json:
              schema:
                type: object
        400:
          description: Invalid GeoJSON Data
        401:
          description: Unauthorized
      security:
        - ApiKeyAuthorizer: [ ]
  /apikeys:
    get:
      tags:
        - api-key
      summary: List api keys
      description: List api keys. Requires admin or sysadmin permissions.
      operationId: getApiKeys
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ApiKey'
        401:
          description: Unauthorized
      security:
        - ApiKeyAuthorizer: [ ]
    post:
      tags:
        - api-key
      summary: Request api key creation
      description: Request api key creation. Requires admin or sysadmin permissions.
      operationId: crateApiKey
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiKey'
        401:
          description: Unauthorized
      security:
        - ApiKeyAuthorizer: [ ]
  /apikeys/{key}:
    delete:
      tags:
        - api-key
      summary: Delete an API key
      description: Delete an API key. Requires admin or sysadmin permissions.
      operationId: deleteApiKey
      responses:
        200:
          description: successful operation
        400:
          description: Invalid parameter
        401:
          description: Unauthorized
      security:
        - ApiKeyAuthorizer: [ ]
  /journals:
    get:
      tags:
        - journal
      summary: Get asset event journal page
      description: Returns journal data in either JSON or Excel format based on Accept header.
      operationId: getJournalPage
      parameters:
        - $ref: '#/components/parameters/journalStartKey'
        - $ref: '#/components/parameters/journalLimit'
        - $ref: '#/components/parameters/journalStart'
        - $ref: '#/components/parameters/journalEnd'
        - in: header
          name: Accept
          schema:
            type: string
            enum:
              - application/json
              - application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
            default: application/json
          description: >-
            Determines response format:
            - `application/json` - Returns JSON data (default)
            - `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` - Returns Excel file

      responses:
        200:
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JournalResponse'
            application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
              schema:
                type: string
                format: binary
          headers:
            Content-Type:
              schema:
                type: string
              example: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
      security:
        - ApiKeyAuthorizer: [ ]
  /site:
    get:
      tags:
        - location
      summary: Get locations
      description: Get locations.
      operationId: getLocations
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Site'
        401:
          description: Unauthorized
      security:
        - ApiKeyAuthorizer: [ ]
    post:
      tags:
        - location
      summary: Create or update location
      description: Create or update location. Requires admin permissions.
      operationId: upsertLocation
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Site'
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Site'
        400:
          description: Invalid site data
        401:
          description: Unauthorized
      security:
        - ApiKeyAuthorizer: [ ]
  /site/{id}:
    delete:
      tags:
        - location
      summary: Delete location
      description: Delete location. Requires admin permissions.
      operationId: deleteLocation
      responses:
        204:
          description: successful operation
        400:
          description: Invalid parameter
        401:
          description: Unauthorized
      security:
        - ApiKeyAuthorizer: [ ]
  /asset-type:
    get:
      tags:
        - asset-types
      summary: Get asset types
      description: Get asset types.
      operationId: getAssetTypes
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AssetType'
        401:
          description: Unauthorized
      security:
        - ApiKeyAuthorizer: [ ]
    post:
      tags:
        - asset-types
      summary: Update asset types
      description: Update asset types. Requires admin permissions.
      operationId: updateAssetTypes
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AssetType'
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AssetType'
        400:
          description: Asset type validation failed
        401:
          description: Unauthorized
      security:
        - ApiKeyAuthorizer: [ ]
  /asset-schema:
    get:
      tags:
        - asset-schemas
      summary: Get asset schemas
      description: Get asset schemas.
      operationId: getAssetSchemas
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AssetSchema'
        401:
          description: Unauthorized
      security:
        - ApiKeyAuthorizer: [ ]
    post:
      tags:
        - asset-schemas
      summary: Update asset schemas
      description: Get asset schemas.
      operationId: updateAssetSchemas
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: '#/components/schemas/AssetSchema'
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AssetSchema'
        400:
          description: body validation failure
        401:
          description: Unauthorized
      security:
        - ApiKeyAuthorizer: [ ]

  # --- Shuttle Endpoints ---
  /shuttles:
    get:
      tags:
        - shuttles
      summary: List all shuttle profiles
      operationId: listShuttles
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ShuttleProfile'
        401:
          description: Unauthorized
      security:
        - ApiKeyAuthorizer: [ ]
    post:
      tags:
        - shuttles
      summary: Create a shuttle profile
      operationId: createShuttle
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateShuttleRequest'
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ShuttleProfile'
        400:
          description: Bad request
        401:
          description: Unauthorized
        409:
          description: Shuttle profile already exists
      security:
        - ApiKeyAuthorizer: [ ]
  /shuttles/{shuttleId}:
    get:
      tags:
        - shuttles
      summary: Get shuttle profile by ID
      operationId: getShuttle
      parameters:
        - $ref: '#/components/parameters/shuttleId'
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ShuttleProfile'
        401:
          description: Unauthorized
        404:
          description: Shuttle not found
      security:
        - ApiKeyAuthorizer: [ ]
    put:
      tags:
        - shuttles
      summary: Update shuttle profile
      operationId: updateShuttle
      parameters:
        - $ref: '#/components/parameters/shuttleId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateShuttleRequest'
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ShuttleProfile'
        401:
          description: Unauthorized
        404:
          description: Shuttle not found
      security:
        - ApiKeyAuthorizer: [ ]
    delete:
      tags:
        - shuttles
      summary: Delete shuttle profile
      operationId: deleteShuttle
      parameters:
        - $ref: '#/components/parameters/shuttleId'
      responses:
        204:
          description: successful operation
        401:
          description: Unauthorized
        404:
          description: Shuttle not found
      security:
        - ApiKeyAuthorizer: [ ]
  /shuttles/{shuttleId}/status:
    put:
      tags:
        - shuttles
      summary: Toggle shuttle on-duty/off-duty status
      operationId: updateShuttleStatus
      parameters:
        - $ref: '#/components/parameters/shuttleId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - isOnDuty
              properties:
                isOnDuty:
                  type: boolean
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ShuttleProfile'
        400:
          description: Bad request
        401:
          description: Unauthorized
        404:
          description: Shuttle not found
      security:
        - ApiKeyAuthorizer: [ ]
  /shuttles/{shuttleId}/tickets:
    get:
      tags:
        - shuttles
      summary: List tickets currently assigned to a shuttle
      description: Returns the activities currently assigned to the specified shuttle (non-closed tickets).
      operationId: getShuttleTickets
      parameters:
        - $ref: '#/components/parameters/shuttleId'
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ActivityModel'
        401:
          description: Unauthorized
        404:
          description: Shuttle not found
      security:
        - ApiKeyAuthorizer: [ ]

  # --- Linked Move Endpoints ---
  /activities/{id}/link/{successorId}:
    post:
      tags:
        - linked-moves
      summary: Link two activities (predecessor → successor)
      operationId: linkActivity
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Predecessor activity ID
        - name: successorId
          in: path
          required: true
          schema:
            type: string
          description: Successor activity ID
      responses:
        200:
          description: successful operation
        400:
          description: Chain too long or invalid link
        401:
          description: Unauthorized
        409:
          description: Link would create a cycle
      security:
        - ApiKeyAuthorizer: [ ]
  /activities/{id}/link:
    get:
      tags:
        - linked-moves
      summary: Get linked move details for an activity
      operationId: getActivityLink
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LinkedMove'
        401:
          description: Unauthorized
      security:
        - ApiKeyAuthorizer: [ ]
    delete:
      tags:
        - linked-moves
      summary: Unlink an activity from its chain
      operationId: unlinkActivity
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        200:
          description: successful operation
        401:
          description: Unauthorized
      security:
        - ApiKeyAuthorizer: [ ]

  # --- Capability Endpoints ---
  /capabilities:
    get:
      tags:
        - capabilities
      summary: List all capabilities
      operationId: listCapabilities
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Capability'
        401:
          description: Unauthorized
      security:
        - ApiKeyAuthorizer: [ ]
    post:
      tags:
        - capabilities
      summary: Create or update a capability
      operationId: upsertCapability
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Capability'
      responses:
        200:
          description: successful operation
        400:
          description: Bad request
        401:
          description: Unauthorized
      security:
        - ApiKeyAuthorizer: [ ]
  /capabilities/{name}:
    delete:
      tags:
        - capabilities
      summary: Delete a capability by name
      operationId: deleteCapability
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        204:
          description: successful operation
        401:
          description: Unauthorized
        404:
          description: Capability not found
      security:
        - ApiKeyAuthorizer: [ ]

  # --- Dispatch Endpoints ---
  /dispatch/queue:
    get:
      tags:
        - dispatch
      summary: List queued tickets with urgency scores
      operationId: getDispatchQueue
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/DispatchQueueItem'
        401:
          description: Unauthorized
      security:
        - ApiKeyAuthorizer: [ ]
  /dispatch/stats:
    get:
      tags:
        - dispatch
      summary: Get dispatch queue statistics
      operationId: getDispatchStats
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DispatchStats'
        401:
          description: Unauthorized
      security:
        - ApiKeyAuthorizer: [ ]
  /dispatch/enqueue/{activityId}:
    post:
      tags:
        - dispatch
      summary: Manually enqueue an activity for dispatch
      operationId: enqueueDispatch
      parameters:
        - name: activityId
          in: path
          required: true
          schema:
            type: string
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DispatchQueueItem'
        401:
          description: Unauthorized
        404:
          description: Activity not found
      security:
        - ApiKeyAuthorizer: [ ]
  /dispatch/settings:
    put:
      tags:
        - dispatch
      summary: Update dispatch settings (enable/disable, configuration)
      operationId: updateDispatchSettings
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - enabled
              properties:
                enabled:
                  type: boolean
                  description: Toggle auto-dispatch on/off
                config:
                  $ref: '#/components/schemas/DispatchConfig'
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                type: object
                properties:
                  settings:
                    type: object
                    additionalProperties: true
        401:
          description: Unauthorized
        404:
          description: Tenant not found
      security:
        - ApiKeyAuthorizer: [ ]


components:
  schemas:
    EmptyString:
      type: string
      maxLength: 0
      example: ""
    AssetSummary:
      type: object
      required:
        - assetId
        - searchId
        - created
        - timestamp
        - expiration
        - latitude
        - longitude
      properties:
        assetId:
          type: string
        searchId:
          type: string
        created:
          type: string
          format: date-time
        timestamp:
          type: number
        expiration:
          type: number
        leading:
          type: number
          nullable: true
        latitude:
          type: number
        longitude:
          type: number
        type:
          type: string
          nullable: true
        isLoaded:
          type: boolean
          nullable: true
        isLinked:
          type: boolean
          nullable: true
        image:
          type: string
          nullable: true
        source:
          type: string
          nullable: true
        linkedId:
          type: string
          nullable: true
        inGate:
          type: number
          nullable: true
        outGate:
          type: number
          nullable: true
    ActivitySummary:
      type: object
      allOf:
        - $ref: '#/components/schemas/Model'
        - $ref: '#/components/schemas/Activity'
        - $ref: '#/components/schemas/ActivityMetadata'
      required:
        - id          # From Model
        - name        # From Activity
        - type        # From Activity
        - status      # From Activity
        - data        # From Activity
        - created     # From ActivityMetadata (now required)
        - createdBy   # From ActivityMetadata (now required)
        - updated     # From ActivityMetadata (now required)
        - updatedBy   # From ActivityMetadata (now required)
    Model:
      type: object
      properties:
        id:
          type: string
      required:
        - id
    Activity:
      type: object
      properties:
        name:
          type: string
        type:
          type: string
        status:
          type: string
        data:
          type: object
          additionalProperties: true
          description: Flexible data bag. May contain requiredCapabilities (string array) for dispatch matching.
        assignee:
          type: string
          nullable: true
        dueDate:
          type: string
          format: date-time
          nullable: true
        labels:
          type: array
          items:
            $ref: '#/components/schemas/Label'
        sequence:
          type: integer
          nullable: true
        start:
          type: string
          format: date-time
          nullable: true
        end:
          type: string
          format: date-time
          nullable: true
        duration:
          type: integer
          nullable: true
        linkedMove:
          $ref: '#/components/schemas/LinkedMove'
        dispatchStatus:
          type: string
          enum:
            - queued
            - unassignable
          nullable: true
          description: |
            Auto-dispatch state marker. One of:
            * `queued` — ticket is currently in the dispatch queue awaiting assignment.
            * `unassignable` — dispatch exhausted its retry attempts without finding a shuttle; requires manual assignment.
            * `null` (or omitted) — ticket is not participating in auto-dispatch (manually assigned, not yet enqueued, or auto-dispatch is disabled for the tenant).
            Cleared automatically when a Dispatch or Admin user manually assigns the ticket.
      required:
        - name
        - type
        - status
        - data
    ActivityMetadata:
      type: object
      properties:
        created:
          type: string
          format: date-time
        createdBy:
          type: string
        updated:
          type: string
          format: date-time
        updatedBy:
          type: string
    ActivityHistory:
      type: object
      properties:
        history:
          type: array
          items:
            type: object
            additionalProperties: true
    ActivityModel:
      type: object
      allOf:
        - $ref: '#/components/schemas/Model'
        - $ref: '#/components/schemas/Activity'
        - $ref: '#/components/schemas/ActivityMetadata'
        - $ref: '#/components/schemas/ActivityHistory'
      required:
        - id
        - name
        - type
        - status
        - data
        - created
        - createdBy
        - updated
        - updatedBy
        - history
    ActivityRequest:
      type: object
      allOf:
        - $ref: '#/components/schemas/Activity'
        - type: object
          properties:
            id:
              $ref: '#/components/schemas/Model/properties/id'
              nullable: true
    Label:
      type: object
      properties:
        name:
          type: string
          nullable: true
        label:
          type: string
        priority:
          type: integer
          minimum: 0
          maximum: 3
          nullable: true
          description: |
            Optional dispatch priority used by the Auto-Dispatch algorithm to order tickets.
            Lower number = higher priority (0 = Critical, 1 = High, 2 = Normal, 3 = Low).
            Labels without a priority value have no effect on dispatch ordering.
    User:
      type: object
      properties:
        username:
          type: string
          description: The user's unique identifier
        groups:
          type: array
          items:
            type: string
          description: List of groups the user belongs to
          nullable: true
        status:
          type: string
          enum: [ active, invited ]
          description: Current account status
          nullable: true
        firstName:
          type: string
          description: User's first name
          nullable: true
        lastName:
          type: string
          description: User's last name
          nullable: true
      required:
        - username
    UserRequest:
      type: object
      properties:
        username:
          type: string
          description: Unique username for the new user
          example: "johndoe123"
        groups:
          type: array
          items:
            type: string
          description: List of groups the user should belong to
          nullable: true
        resend:
          type: boolean
          description: Whether to resend invitation if user exists
          default: false
          nullable: true
        lastName:
          type: string
          description: User's last name
          example: "Doe"
          nullable: true
        firstName:
          type: string
          description: User's first name
          example: "John"
          nullable: true
      required:
        - username
    ApiKey:
      type: object
      description: Represents an API key with its metadata
      properties:
        key:
          type: string
          description: The actual API key value
          example: "sk_1234567890abcdef"
        tenantId:
          type: string
          description: The tenant/organization this key belongs to
          example: "org_12345"
        created:
          type: string
          format: date-time
          description: When the key was created
          example: "2023-01-15T09:30:00Z"
        createdBy:
          type: string
          example: "user@example.com"
      required:
        - key
        - tenantId
        - created
        - createdBy
    AssetLocationRequest:
      type: object
      description: Request payload for recording or updating an asset's location
      properties:
        assetId:
          type: string
          description: Unique identifier of the asset
          example: "asset-12345"
        timestamp:
          type: number
          format: int64
          description: Unix timestamp in milliseconds when the location was recorded
          example: 1678901234567
        latitude:
          type: number
          format: double
          description: Geographic latitude coordinate
          example: 40.7128
        longitude:
          type: number
          format: double
          description: Geographic longitude coordinate
          example: -74.0060
        type:
          oneOf:
            - type: string
          description: Type of asset or custom type string
          nullable: true
          example: "TRUCK"
        isLoaded:
          type: boolean
          description: Indicates if the asset is currently loaded
          nullable: true
          example: true
        isLinked:
          type: boolean
          description: Indicates if the asset is linked to another asset
          nullable: true
          example: false
        image:
          type: string
          description: URL or base64 encoded image of the asset at location
          nullable: true
          example: "https://example.com/image.jpg"
        source:
          type: string
          description: Source system or device that provided the location data
          nullable: true
          example: "GPS-Device-123"
        linkedId:
          type: string
          description: ID of the linked asset if isLinked is true
          nullable: true
          example: "asset-67890"
        event:
          type: string
          description: Event type associated with this location update
          nullable: true
      required:
        - assetId
        - timestamp
        - latitude
        - longitude
    JournalResponse:
      type: object
      description: Paginated response containing journal entries
      properties:
        lastKey:
          type: string
          description: Pagination token for fetching the next page of results
          nullable: true
          example: "eyJ0aW1lc3RhbXAiOjE2ODAwMDAwMDAwLCJhc3NldElkIjoiYXNzZXQtMTIzIn0="
        data:
          type: array
          description: List of journal entries
          items:
            $ref: '#/components/schemas/JournalEntry'
          minItems: 0
      required:
        - data
    JournalEntry:
      type: object
      description: A single journal/audit log entry
      properties:
        assetId:
          type: string
          description: ID of the asset this entry relates to
          example: "asset-12345"
        timestamp:
          type: number
          format: int64
          description: Unix timestamp in milliseconds when the event occurred
          example: 1678901234567
        created:
          type: string
          format: date-time
          description: When this journal entry was created in the system
          example: "2023-01-15T09:30:00Z"
        eventType:
          type: string
          description: Type of event that was recorded
        data:
          type: object
          additionalProperties: true
          description: Additional event-specific data
        linkedId:
          type: string
          description: ID of a related asset or entity if applicable
          nullable: true
          example: "asset-67890"
      required:
        - assetId
        - timestamp
        - created
        - eventType
        - data
    Site:
      type: object
      description: A site containing multiple facilities
      properties:
        id:
          type: string
          description: Unique identifier for the site
          example: "site-12345"
        name:
          type: string
          description: Name of the site
          example: "Central Warehouse"
        facilities:
          type: array
          description: List of facilities at this site
          items:
            $ref: '#/components/schemas/Facility'
          minItems: 1
      required:
        - id
        - name
        - facilities
    Facility:
      type: object
      description: A facility within a site
      properties:
        name:
          type: string
          description: Name of the facility
          example: "Loading Dock A"
        position:
          $ref: '#/components/schemas/Position'
          nullable: true
        locations:
          type: array
          description: Specific locations within this facility
          items:
            $ref: '#/components/schemas/Location'
          nullable: true
      required:
        - name
    Position:
      type: object
      description: Geographic coordinates
      properties:
        latitude:
          type: number
          format: double
          description: Latitude in decimal degrees
          example: 40.7128
          minimum: -90
          maximum: 90
        longitude:
          type: number
          format: double
          description: Longitude in decimal degrees
          example: -74.0060
          minimum: -180
          maximum: 180
      required:
        - latitude
        - longitude
    Location:
      type: object
      description: A specific named location within a facility
      properties:
        name:
          type: string
          description: Name of the location
          example: "Bay 3"
        position:
          $ref: '#/components/schemas/Position'
      required:
        - name
        - position
    AssetType:
      type: object
      required:
        - id
        - name
      properties:
        id:
          type: string
        name:
          type: string
          description: Human-readable name of the type
    AssetSchema:
      type: object
      required:
        - name
        - scope
        - schema
      properties:
        name:
          type: string
          description: Name of the asset schema
          example: "Product1"
        scope:
          type: array
          items:
            type: string
            description: Asset types, e.g., 'global' or any asset types
            example: "global"
          description: Array of scope identifiers (e.g., ['global', 'teamA'])
          example:
            - "global"
            - "teamA"
        schema:
          type: string
          description: Serialized JSON schema definition
          example: '{"type": "object", "properties": {"key": {"type": "string"}}}'

    # --- Auto-Dispatch Schemas ---
    ShuttleProfile:
      type: object
      properties:
        tenantId:
          type: string
        shuttleId:
          type: string
          description: Cognito username (email), always lowercase
        firstName:
          type: string
          nullable: true
        lastName:
          type: string
          nullable: true
        isOnDuty:
          type: boolean
        capabilities:
          type: array
          items:
            type: string
        lastPosition:
          type: object
          nullable: true
          properties:
            latitude:
              type: number
            longitude:
              type: number
        lastPositionTimestamp:
          type: string
          format: date-time
          nullable: true
        activeTicketCount:
          type: integer
        created:
          type: string
          format: date-time
        updated:
          type: string
          format: date-time
    CreateShuttleRequest:
      type: object
      required:
        - shuttleId
      properties:
        shuttleId:
          type: string
          description: Email address of the Cognito user
        firstName:
          type: string
        lastName:
          type: string
        capabilities:
          type: array
          items:
            type: string
    UpdateShuttleRequest:
      type: object
      properties:
        firstName:
          type: string
        lastName:
          type: string
        capabilities:
          type: array
          items:
            type: string
    LinkedMove:
      type: object
      nullable: true
      properties:
        predecessorId:
          type: string
          nullable: true
          description: Activity ID that must close before this one can start
        successorId:
          type: string
          nullable: true
          description: Activity ID unblocked when this one closes
    Capability:
      type: object
      required:
        - name
      properties:
        name:
          type: string
          description: Capability tag name (case-insensitive)
        description:
          type: string
          nullable: true
    DispatchQueueItem:
      type: object
      description: |
        A ticket waiting in the dispatch queue. The dispatch algorithm computes a single
        combined score per (ticket × shuttle) pair on a 0–100 scale (higher = better assignment)
        using five tenant-configurable weights — three ticket-side (sequence, due date, label
        priority) and two shuttle/pair-side (proximity, workload). The fields below describe
        the ticket-side portion of that score: the raw inputs stored at enqueue time, the
        rank-normalized 0–100 components computed across the current queue, and each
        component's weighted contribution.
      properties:
        tenantId:
          type: string
        activityId:
          type: string
        activityType:
          type: string
        originLatitude:
          type: number
          nullable: true
        originLongitude:
          type: number
          nullable: true
        requiredCapabilities:
          type: array
          items:
            type: string
        enqueuedAt:
          type: string
          format: date-time
        attempts:
          type: integer
        lastAttempt:
          type: string
          format: date-time
          nullable: true
        sequenceRank:
          type: number
          nullable: true
          description: Raw `sequence` value on the activity at enqueue time (the dispatcher's manual ordering).
        dueDateScore:
          type: number
          nullable: true
          description: Raw hours-until-due at enqueue time (negative = overdue; defaults to a sentinel value when the ticket has no due date).
        labelPriorityScore:
          type: number
          nullable: true
          description: Minimum `priority` value among the ticket's labels at enqueue time (0 = Critical … 3 = Low; defaults to a sentinel value when the ticket has no prioritized labels).
        sequenceComponent:
          type: number
          nullable: true
          description: Percentile rank of `sequenceRank` across the current queue, normalized to 0–100. 100 = most urgent.
        dueDateComponent:
          type: number
          nullable: true
          description: Percentile rank of `dueDateScore` across the current queue, normalized to 0–100. 100 = most urgent. Tickets with no due date rank last (0).
        labelComponent:
          type: number
          nullable: true
          description: Percentile rank of `labelPriorityScore` across the current queue, normalized to 0–100. 100 = highest-priority label.
        sequenceContribution:
          type: number
          nullable: true
          description: '`sequenceComponent × (sequenceWeightPct / 100)` — this ticket''s weighted sequence contribution to its overall urgency.'
        dueDateContribution:
          type: number
          nullable: true
          description: '`dueDateComponent × (dueDateWeightPct / 100)` — this ticket''s weighted due-date contribution.'
        labelContribution:
          type: number
          nullable: true
          description: '`labelComponent × (labelWeightPct / 100)` — this ticket''s weighted label-priority contribution.'
        ticketUrgencyScore:
          type: number
          description: |
            Weighted sum of the three ticket-side contributions; higher = more urgent. Its
            ceiling is the sum of the three ticket-side weight percentages (e.g. 60 with the
            default 20/25/15 weights), NOT a fixed 100 — clients that want a clean 0–100
            priority score divide by that weight sum and rescale. This is the ticket-side
            portion of the combined (ticket × shuttle) score used by the dispatch algorithm;
            it drives the queue's sort order (most-urgent first) and the UI's hover-over
            breakdown. Proximity and workload contributions are pair-specific and only appear
            at scoring time.
        urgencyScore:
          type: number
          description: Legacy alias kept equal to `ticketUrgencyScore` (higher = more urgent) so existing consumers keep working.
    DispatchStats:
      type: object
      properties:
        queueDepth:
          type: integer
        averageWaitTimeMinutes:
          type: number
          nullable: true
        shuttleUtilization:
          type: array
          items:
            type: object
            properties:
              shuttleId:
                type: string
              activeTicketCount:
                type: integer
              maxTickets:
                type: integer
              utilizationPct:
                type: number
        unassignableCount:
          type: integer
    DispatchConfig:
      type: object
      description: |
        Tenant-level tuning for the auto-dispatch algorithm. The five `*WeightPct` fields
        must sum to ~100 (the API tolerates ±1 for rounding). They control each factor's
        contribution to the combined (ticket × shuttle) score, which is a 0–100 value
        where higher = better assignment.
      properties:
        maxRetryAttempts:
          type: integer
          description: Dispatch cycles before marking a ticket unassignable (default 10).
        maxConcurrentTicketsPerShuttle:
          type: integer
          description: Hard capacity cap per shuttle (default 5).
        sequenceWeightPct:
          type: integer
          minimum: 0
          maximum: 100
          description: Weight for the dispatcher's manual sequence ordering (default 20).
        dueDateWeightPct:
          type: integer
          minimum: 0
          maximum: 100
          description: Weight for hours-until-due (default 25).
        labelWeightPct:
          type: integer
          minimum: 0
          maximum: 100
          description: Weight for label priority (default 15).
        proximityWeightPct:
          type: integer
          minimum: 0
          maximum: 100
          description: Weight for distance from the shuttle's projected end-of-route position to the ticket origin (default 25).
        workloadWeightPct:
          type: integer
          minimum: 0
          maximum: 100
          description: Weight for the shuttle's current active ticket count vs. capacity (default 15).
  parameters:
    shuttleId:
      name: shuttleId
      in: path
      required: true
      schema:
        type: string
      description: Shuttle profile ID (email address, URL-encoded)
    nextToken:
      name: nextToken
      in: query
      description: Pagination token for fetching the next page
      schema:
        type: string
      required: false
    limit:
      name: limit
      in: query
      description: Maximum number of items to return
      schema:
        type: integer
        minimum: 1
        default: 50
      required: false
    queryCriteria:
      name: queryCriteria
      in: query
      description: Filter criteria for activities
      style: deepObject
      schema:
        type: object
        properties:
          createdStartDate:
            type: string
            format: date-time
          createdEndDate:
            type: string
            format: date-time
          completedStartDate:
            type: string
            format: date-time
          completedEndDate:
            type: string
            format: date-time
          status:
            type: string
            enum: [todo, problem, inprogress, closed]
        required: false
    properties:
      name: properties
      in: query
      description: Properties to include in the response
      style: form
      schema:
        type: array
        items:
          type: string
          enum: [ id, name, type, status, data, assignee, dueDate, created, createdBy, updated, updatedBy ] # All keys of ActivitySummary
      required: false
    journalStartKey:
      name: startKey
      in: query
      description: Pagination token to start fetching results from
      schema:
        type: string
        nullable: true
      required: false
      example: "eyJ0aW1lc3RhbXAiOjE2ODAwMDAwMDAwLCJhc3NldElkIjoiYXNzZXQtMTIzIn0="
    journalLimit:
      name: limit
      in: query
      description: Maximum number of entries to return
      schema:
        type: integer
      required: false
      example: 50
    journalStart:
      name: start
      in: query
      description: ISO 8601 date string for the earliest entry to include (inclusive)
      schema:
        type: string
        format: date-time
        pattern: '^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$'
        nullable: true
      required: false
      example: "2023-03-15T09:30:00Z"
    journalEnd:
      name: end
      in: query
      description: ISO 8601 date string for the latest entry to include (exclusive)
      schema:
        type: string
        format: date-time
        pattern: '^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$'
        nullable: true
      required: false
      example: "2023-03-16T18:45:00Z"
  securitySchemes:
    ApiKeyAuthorizer:
      type: apiKey
      name: x-api-key
      in: header