openapi: 3.1.0
info:
  title: iotee Fleet Public API
  version: "1.0.0"
  description: |
    Read-only public API for iotee fleet customers: vehicles, drivers, trips,
    live vehicle location, and fleet statistics.

    Authenticate with an API key (`X-API-Key` or `Authorization: Bearer`) — see
    **Authorize** below for the key format. List endpoints accept `page` and
    `limit` query params.

    Full guides — authentication, rate limits & packages, errors, pagination —
    live at the [docs site](https://docs.iotee.app). This reference is the
    single source of truth for endpoints, schemas, and the try-it console.
  contact:
    name: iotee Fleet API Support
    url: https://iotee.app

servers:
  - url: https://api.iotee.app/v1/public
    description: Production
  - url: http://localhost:3015/v1/public
    description: Local development

security:
  - ApiKeyHeader: []
  - BearerAuth: []

tags:
  - name: Account
  - name: Vehicles
  - name: Drivers
  - name: Trips
  - name: Live
  - name: Stats
  - name: Geofences
  - name: Notifications
  - name: Fuel
  - name: Maintenance
  - name: Parts
  - name: Inventory
  - name: ISO 15143-3

paths:
  /me:
    get:
      tags: [Account]
      summary: Get current API key info, package, and usage
      operationId: getMe
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  data:
                    type: object
                    properties:
                      transport_uuid: { type: string }
                      key_uuid: { type: string }
                      key_name: { type: string }
                      key_prefix: { type: string }
                      scopes:
                        type: array
                        items: { type: string }
                      usage_this_month: { type: integer }
                      package:
                        type: object
                        nullable: true
                        properties:
                          name: { type: string }
                          slug: { type: string }
                          monthly_quota: { type: integer }
                          rate_limit_per_min: { type: integer }
                          tenant_usage: { type: integer }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /vehicles:
    get:
      tags: [Vehicles]
      summary: List vehicles
      operationId: listVehicles
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/Limit"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/PaginatedEnvelope"
                  - type: object
                    properties:
                      data:
                        type: array
                        items: { $ref: "#/components/schemas/Vehicle" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /vehicles/{uuid}:
    get:
      tags: [Vehicles]
      summary: Get vehicle details
      operationId: getVehicle
      parameters:
        - $ref: "#/components/parameters/UuidPath"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  data: { $ref: "#/components/schemas/VehicleDetail" }
        "404": { $ref: "#/components/responses/NotFound" }

  /vehicles/{uuid}/locations:
    get:
      tags: [Vehicles]
      summary: Get GPS location history for a vehicle in a date range (max 7 days)
      operationId: getVehicleLocations
      parameters:
        - $ref: "#/components/parameters/UuidPath"
        - name: start_date
          in: query
          required: true
          schema: { type: string, format: date-time }
          description: RFC3339 timestamp
        - name: end_date
          in: query
          required: true
          schema: { type: string, format: date-time }
          description: RFC3339 timestamp, must be within 7 days of start_date
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  data:
                    type: object
                    properties:
                      vehicle_uuid: { type: string }
                      trip_count: { type: integer }
                      points:
                        type: array
                        items: { $ref: "#/components/schemas/LocationPoint" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "404": { $ref: "#/components/responses/NotFound" }

  /drivers:
    get:
      tags: [Drivers]
      summary: List drivers
      operationId: listDrivers
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/Limit"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/PaginatedEnvelope"
                  - type: object
                    properties:
                      data:
                        type: array
                        items: { $ref: "#/components/schemas/Driver" }

  /drivers/{uuid}:
    get:
      tags: [Drivers]
      summary: Get driver details
      operationId: getDriver
      parameters:
        - $ref: "#/components/parameters/UuidPath"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  data: { $ref: "#/components/schemas/Driver" }
        "404": { $ref: "#/components/responses/NotFound" }

  /trips:
    get:
      tags: [Trips]
      summary: List GPS-derived trips
      operationId: listTrips
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/Limit"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/PaginatedEnvelope"
                  - type: object
                    properties:
                      data:
                        type: array
                        items: { $ref: "#/components/schemas/Trip" }

  /trips/{uuid}:
    get:
      tags: [Trips]
      summary: Get trip details
      operationId: getTrip
      parameters:
        - $ref: "#/components/parameters/UuidPath"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  data: { $ref: "#/components/schemas/TripDetail" }
        "404": { $ref: "#/components/responses/NotFound" }

  /trips/{uuid}/route:
    get:
      tags: [Trips]
      summary: Get the GPS route (point path) for a trip
      operationId: getTripRoute
      parameters:
        - $ref: "#/components/parameters/UuidPath"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/LocationPoint" }
        "404": { $ref: "#/components/responses/NotFound" }

  /live/vehicles:
    get:
      tags: [Live]
      summary: List latest known position for all vehicles
      operationId: listLiveVehicles
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/Limit"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/PaginatedEnvelope"
                  - type: object
                    properties:
                      data:
                        type: array
                        items: { $ref: "#/components/schemas/LiveVehicle" }

  /live/vehicles/{uuid}:
    get:
      tags: [Live]
      summary: Get latest known position + telemetry for one vehicle
      operationId: getLiveVehicle
      parameters:
        - $ref: "#/components/parameters/UuidPath"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  data: { $ref: "#/components/schemas/LiveVehicleDetail" }
        "404": { $ref: "#/components/responses/NotFound" }

  /stats/fleet:
    get:
      tags: [Stats]
      summary: Fleet-wide totals and live status breakdown
      operationId: getFleetStats
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  data:
                    type: object
                    properties:
                      totals: { $ref: "#/components/schemas/FleetTotals" }
                      live_status: { $ref: "#/components/schemas/FleetLiveStatus" }

  /stats/vehicles/{uuid}:
    get:
      tags: [Stats]
      summary: Per-vehicle statistics (today, trips, hours, distance/fuel by month)
      operationId: getVehicleStats
      parameters:
        - $ref: "#/components/parameters/UuidPath"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  data:
                    type: object
                    properties:
                      vehicle_uuid: { type: string }
                      today: { $ref: "#/components/schemas/VehicleTodayStats" }
                      trips: { $ref: "#/components/schemas/VehicleTripCounts" }
                      hours_driven: { $ref: "#/components/schemas/VehicleHoursDriven" }
                      distance_by_month:
                        type: array
                        items: { $ref: "#/components/schemas/DistanceByMonth" }
                      fuel_by_month:
                        type: array
                        items: { $ref: "#/components/schemas/FuelByMonth" }
        "404": { $ref: "#/components/responses/NotFound" }

  /geofences:
    get:
      tags: [Geofences]
      summary: List geofences
      operationId: listGeofences
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/Limit"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/PaginatedEnvelope"
                  - type: object
                    properties:
                      data:
                        type: array
                        items: { $ref: "#/components/schemas/Geofence" }

  /notifications:
    get:
      tags: [Notifications]
      summary: List transport notifications (fleet/telemetry events)
      description: >
        Tenant-scoped transport notifications only (overspeed, geofence, SOS,
        etc.). Per-user maintenance notifications are not exposed on the public
        API. Requires the `notification.view` scope.
      operationId: listNotifications
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/Limit"
        - name: only_unread
          in: query
          schema: { type: boolean }
        - name: vehicle_uuid
          in: query
          schema: { type: string }
        - name: driver_uuid
          in: query
          schema: { type: string }
        - name: priorities
          in: query
          description: Comma-separated priorities
          schema: { type: string }
        - name: types
          in: query
          description: Comma-separated notification types
          schema: { type: string }
        - name: start_date
          in: query
          schema: { type: string, format: date-time }
        - name: end_date
          in: query
          schema: { type: string, format: date-time }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/PaginatedEnvelope"
                  - type: object
                    properties:
                      data:
                        type: array
                        items: { $ref: "#/components/schemas/Notification" }

  /notifications/{uuid}:
    get:
      tags: [Notifications]
      summary: Get a single transport notification
      operationId: getNotification
      parameters:
        - $ref: "#/components/parameters/UuidPath"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  data: { $ref: "#/components/schemas/Notification" }
        "404": { $ref: "#/components/responses/NotFound" }

  /fuel/transactions:
    get:
      tags: [Fuel]
      summary: List fuel transactions
      description: >
        Card-import and manually recorded fuel transactions. Requires the
        `fleet.reports.view` scope.
      operationId: listFuelTransactions
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/Limit"
        - name: vehicle_uuid
          in: query
          schema: { type: string }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/PaginatedEnvelope"
                  - type: object
                    properties:
                      data:
                        type: array
                        items: { $ref: "#/components/schemas/FuelTransaction" }

  /fuel/transactions/{uuid}:
    get:
      tags: [Fuel]
      summary: Get a single fuel transaction
      operationId: getFuelTransaction
      parameters:
        - $ref: "#/components/parameters/UuidPath"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  data: { $ref: "#/components/schemas/FuelTransaction" }
        "404": { $ref: "#/components/responses/NotFound" }

  /fuel/entries:
    post:
      tags: [Fuel]
      summary: Create a manual fuel entry
      description: >
        Records a manual fuel fill for a vehicle. The odometer reading must be
        greater than the vehicle's previous entry. Emits a `fuel.transaction`
        webhook event. Requires the `fleet.reports.view` scope.
      operationId: createFuelEntry
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreateFuelEntryRequest" }
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  data: { $ref: "#/components/schemas/FuelEntry" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "404": { $ref: "#/components/responses/NotFound" }

  /maintenance/schedules:
    get:
      tags: [Maintenance]
      summary: List maintenance schedules with urgency status
      description: Requires the `maintenance.schedule.view` scope.
      operationId: listMaintenanceSchedules
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/Limit"
        - name: urgency_status
          in: query
          schema: { type: string, enum: [normal, due, overdue] }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/PaginatedEnvelope"
                  - type: object
                    properties:
                      data:
                        type: array
                        items: { $ref: "#/components/schemas/MaintenanceSchedule" }

  /maintenance/job-cards:
    get:
      tags: [Maintenance]
      summary: List maintenance job cards
      description: Requires the `maintenance.job-card.view` scope.
      operationId: listJobCards
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/Limit"
        - name: status
          in: query
          schema:
            type: string
            enum: [pending, assigned, in_progress, completed, approved, rejected, cancelled]
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/PaginatedEnvelope"
                  - type: object
                    properties:
                      data:
                        type: array
                        items: { $ref: "#/components/schemas/JobCard" }
    post:
      tags: [Maintenance]
      summary: Create a maintenance job card
      description: >
        Creates a work order in `pending` status with a generated per-tenant
        job number. Requires the `maintenance.job-card.create` scope.
      operationId: createJobCard
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreateJobCardRequest" }
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  data: { $ref: "#/components/schemas/JobCard" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "404": { $ref: "#/components/responses/NotFound" }

  /maintenance/job-cards/{uuid}:
    get:
      tags: [Maintenance]
      summary: Get a job card (summary level, no line items)
      operationId: getJobCard
      parameters:
        - $ref: "#/components/parameters/UuidPath"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  data: { $ref: "#/components/schemas/JobCard" }
        "404": { $ref: "#/components/responses/NotFound" }
    patch:
      tags: [Maintenance]
      summary: Update a job card (execution states only)
      description: >
        Allows status transitions to `in_progress`, `completed` or `cancelled`
        plus description/notes updates. Assignment and approval states are
        reserved for the internal workflow. Requires the
        `maintenance.job-card.update` scope.
      operationId: updateJobCard
      parameters:
        - $ref: "#/components/parameters/UuidPath"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/UpdateJobCardRequest" }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  data: { $ref: "#/components/schemas/JobCard" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "404": { $ref: "#/components/responses/NotFound" }

  /parts:
    get:
      tags: [Parts]
      summary: List parts catalog with stock levels
      description: Requires the `maintenance.parts.view` scope.
      operationId: listParts
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/Limit"
        - name: category
          in: query
          schema: { type: string }
        - name: low_stock
          in: query
          description: Set to `true` to return only parts at or below their minimum stock level.
          schema: { type: boolean }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/PaginatedEnvelope"
                  - type: object
                    properties:
                      data:
                        type: array
                        items: { $ref: "#/components/schemas/Part" }
    post:
      tags: [Parts]
      summary: Create a catalog part
      description: >
        Part numbers are unique per tenant; duplicates answer 409. Requires
        the `maintenance.parts.create` scope.
      operationId: createPart
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreatePartRequest" }
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  data: { $ref: "#/components/schemas/Part" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "409":
          description: Part number already exists

  /parts/{uuid}:
    get:
      tags: [Parts]
      summary: Get a part with stock levels
      operationId: getPart
      parameters:
        - $ref: "#/components/parameters/UuidPath"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  data: { $ref: "#/components/schemas/Part" }
        "404": { $ref: "#/components/responses/NotFound" }
    patch:
      tags: [Parts]
      summary: Update a part (whitelisted fields)
      description: >
        Updatable: name, description, category, unit_price, current_stock,
        min/max stock levels, unit_of_measure, is_active, is_consumable.
        Requires the `maintenance.parts.update` scope.
      operationId: updatePart
      parameters:
        - $ref: "#/components/parameters/UuidPath"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/UpdatePartRequest" }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  data: { $ref: "#/components/schemas/Part" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "404": { $ref: "#/components/responses/NotFound" }

  /inventory/stock-alerts:
    get:
      tags: [Inventory]
      summary: List stock alerts (low stock / out of stock / overstock)
      description: >
        Defaults to active alerts. Requires the `maintenance.parts.view` scope.
      operationId: listStockAlerts
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/Limit"
        - name: status
          in: query
          schema: { type: string, enum: [active, acknowledged, resolved], default: active }
        - name: alert_type
          in: query
          schema: { type: string, enum: [low_stock, out_of_stock, overstock] }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/PaginatedEnvelope"
                  - type: object
                    properties:
                      data:
                        type: array
                        items: { $ref: "#/components/schemas/StockAlert" }

  /iso15143/Fleet/{pageNumber}:
    get:
      tags: [ISO 15143-3]
      summary: Fleet snapshot (ISO 15143-3 / AEMP 2.0)
      description: >
        Paginated fleet snapshot in the ISO 15143-3 JSON representation
        (PascalCase wire format, page size 100). Blocks with no underlying
        data are omitted, never zero-filled. Requires the `vehicle.view` scope.
      operationId: isoGetFleet
      parameters:
        - $ref: "#/components/parameters/IsoPageNumber"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/IsoFleet" }
        "400": { $ref: "#/components/responses/BadRequest" }

  /iso15143/Fleet/Equipment/{oemName}/{model}/{serialNumber}:
    get:
      tags: [ISO 15143-3]
      summary: Single equipment snapshot
      description: >
        One equipment record, addressed the ISO way by OEM name, model, and
        serial number (the VIN/chassis number, or the vehicle uuid when no VIN
        is recorded). All three must match — case-insensitively — or the
        response is an indistinguishable 404. Requires the `vehicle.view` scope.
      operationId: isoGetEquipment
      parameters:
        - $ref: "#/components/parameters/IsoOemName"
        - $ref: "#/components/parameters/IsoModel"
        - $ref: "#/components/parameters/IsoSerialNumber"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/IsoEquipment" }
        "404": { $ref: "#/components/responses/NotFound" }

  /iso15143/Fleet/Equipment/{oemName}/{model}/{serialNumber}/FaultCodes/{startDateUTC}/{endDateUTC}/{pageNumber}:
    get:
      tags: [ISO 15143-3]
      summary: Fault-code time series
      description: >
        Diagnostic fault codes whose `last_seen_at` falls in the requested
        window (RFC3339 UTC, capped at 14 days). Requires the `vehicle.view`
        scope.
      operationId: isoGetFaultCodes
      parameters:
        - $ref: "#/components/parameters/IsoOemName"
        - $ref: "#/components/parameters/IsoModel"
        - $ref: "#/components/parameters/IsoSerialNumber"
        - $ref: "#/components/parameters/IsoStartDateUTC"
        - $ref: "#/components/parameters/IsoEndDateUTC"
        - $ref: "#/components/parameters/IsoPageNumber"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/IsoFaultCodeSeries" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "404": { $ref: "#/components/responses/NotFound" }

  /iso15143/Fleet/Equipment/{oemName}/{model}/{serialNumber}/Locations/{startDateUTC}/{endDateUTC}/{pageNumber}:
    get:
      tags: [ISO 15143-3]
      summary: Location time series
      description: >
        GPS position history for the requested window (RFC3339 UTC, capped at
        the 7-day history limit shared with `/vehicles/{uuid}/locations`).
        Requires the `vehicle.view` scope.
      operationId: isoGetLocations
      parameters:
        - $ref: "#/components/parameters/IsoOemName"
        - $ref: "#/components/parameters/IsoModel"
        - $ref: "#/components/parameters/IsoSerialNumber"
        - $ref: "#/components/parameters/IsoStartDateUTC"
        - $ref: "#/components/parameters/IsoEndDateUTC"
        - $ref: "#/components/parameters/IsoPageNumber"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/IsoLocationSeries" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "404": { $ref: "#/components/responses/NotFound" }

components:
  securitySchemes:
    ApiKeyHeader:
      type: apiKey
      in: header
      name: X-API-Key
      description: API key (starts with `iot_live_`), issued per package from the dashboard.
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: iot_live_xxxxx
      description: Same API key sent as a Bearer token.

  parameters:
    Page:
      name: page
      in: query
      description: Page number (1-based). Responses return `current_page` and `last_page`.
      schema: { type: integer, default: 1 }
    Limit:
      name: limit
      in: query
      description: Items per page.
      schema: { type: integer, default: 20 }
    UuidPath:
      name: uuid
      in: path
      required: true
      schema: { type: string }
    IsoPageNumber:
      name: pageNumber
      in: path
      required: true
      description: 1-based page number (page size 100).
      schema: { type: integer, minimum: 1 }
    IsoOemName:
      name: oemName
      in: path
      required: true
      description: Vehicle make, matched case-insensitively.
      schema: { type: string }
    IsoModel:
      name: model
      in: path
      required: true
      description: Vehicle model, matched case-insensitively.
      schema: { type: string }
    IsoSerialNumber:
      name: serialNumber
      in: path
      required: true
      description: VIN/chassis number, or vehicle uuid when no VIN is recorded.
      schema: { type: string }
    IsoStartDateUTC:
      name: startDateUTC
      in: path
      required: true
      description: Range start, RFC3339 UTC (URL-encoded).
      schema: { type: string, format: date-time }
    IsoEndDateUTC:
      name: endDateUTC
      in: path
      required: true
      description: Range end, RFC3339 UTC (URL-encoded).
      schema: { type: string, format: date-time }

  responses:
    BadRequest:
      description: Bad request
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ErrorEnvelope" }
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema: { $ref: "#/components/schemas/AuthErrorEnvelope" }
    Forbidden:
      description: Public API not enabled for this tenant, or scope not permitted
      content:
        application/json:
          schema: { $ref: "#/components/schemas/AuthErrorEnvelope" }
    NotFound:
      description: Not found (also returned for cross-tenant resource probes)
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ErrorEnvelope" }
    TooManyRequests:
      description: Rate limit or monthly quota exceeded
      headers:
        Retry-After:
          schema: { type: integer }
      content:
        application/json:
          schema: { $ref: "#/components/schemas/AuthErrorEnvelope" }

  schemas:
    ErrorEnvelope:
      type: object
      properties:
        success: { type: boolean, example: false }
        message: { type: string }

    AuthErrorEnvelope:
      type: object
      properties:
        error: { type: string }

    PaginatedEnvelope:
      type: object
      properties:
        success: { type: boolean }
        total: { type: integer }
        current_page: { type: integer }
        last_page: { type: integer }

    Vehicle:
      type: object
      properties:
        uuid: { type: string }
        license_plate_number: { type: string }
        license_plate_category: { type: string }
        license_plate_state_code: { type: string }
        registration_number: { type: string, nullable: true }
        make: { type: string, nullable: true }
        model: { type: string, nullable: true }
        year: { type: integer, nullable: true }
        chassis_number: { type: string, nullable: true }
        vehicle_type: { type: string }
        vehicle_category: { type: string, nullable: true }
        status: { type: string }
        seating_capacity: { type: integer, nullable: true }
        fuel_tank_capacity: { type: integer, nullable: true }
        created_at: { type: string, format: date-time }

    VehicleDetail:
      allOf:
        - $ref: "#/components/schemas/Vehicle"
        - type: object
          properties:
            engine_number: { type: string, nullable: true }
            insurance_company: { type: string, nullable: true }
            insurance_expiry_date: { type: string, nullable: true }
            registration_expiry_date: { type: string, nullable: true }
            country: { type: string, nullable: true }
            site_location: { type: string, nullable: true }

    Driver:
      type: object
      properties:
        uuid: { type: string }
        first_name: { type: string }
        last_name: { type: string }
        gender: { type: string }
        nationality: { type: string, nullable: true }
        contact_number: { type: string, nullable: true }
        email_address: { type: string, nullable: true }
        license_number: { type: string }
        license_issue_date: { type: string, nullable: true }
        license_expiry_date: { type: string, nullable: true }
        status: { type: string }

    TripDriver:
      type: object
      properties:
        uuid: { type: string }
        first_name: { type: string }
        last_name: { type: string }

    Trip:
      type: object
      properties:
        trip_uuid: { type: string }
        vehicle_uuid: { type: string }
        license_plate_number: { type: string }
        driver: { $ref: "#/components/schemas/TripDriver" }
        start_time: { type: string, format: date-time }
        end_time: { type: string, format: date-time }
        start_location: { type: string }
        end_location: { type: string }
        start_address: { type: string }
        end_address: { type: string }
        distance_km: { type: number }
        duration_seconds: { type: integer }
        idle_duration_seconds: { type: integer }
        fuel_consumption: { type: number }

    TripDetail:
      type: object
      properties:
        trip_uuid: { type: string }
        vehicle_uuid: { type: string }
        driver: { $ref: "#/components/schemas/TripDriver" }
        start_time: { type: string, format: date-time }
        end_time: { type: string, format: date-time, nullable: true }
        start_location: { type: string }
        end_location: { type: string }
        start_address: { type: string }
        end_address: { type: string }
        start_odometer: { type: integer, nullable: true }
        end_odometer: { type: integer, nullable: true }
        start_fuel_ltr: { type: integer }
        end_fuel_ltr: { type: integer }
        total_fuel_consumed: { type: integer }

    LocationPoint:
      type: object
      properties:
        latitude: { type: number }
        longitude: { type: number }
        speed_kmh: { type: integer }
        heading: { type: integer }
        recorded_at: { type: string, format: date-time }

    LiveVehicle:
      type: object
      properties:
        vehicle_uuid: { type: string }
        license_plate_number: { type: string }
        license_plate_category: { type: string }
        license_plate_state_code: { type: string }
        vehicle_type: { type: string }
        driver_name: { type: string, nullable: true }
        latitude: { type: number }
        longitude: { type: number }
        speed_kmh: { type: integer }
        heading: { type: integer }
        ignition: { type: boolean }
        movement_status: { type: integer }
        is_idle: { type: boolean }
        battery_level: { type: integer }
        recorded_at: { type: string, format: date-time, nullable: true }

    LiveVehicleDetail:
      type: object
      properties:
        vehicle_uuid: { type: string }
        license_plate_number: { type: string }
        license_plate_category: { type: string }
        license_plate_state_code: { type: string }
        vehicle_type: { type: string }
        make: { type: string, nullable: true }
        model: { type: string, nullable: true }
        year: { type: integer, nullable: true }
        latitude: { type: number }
        longitude: { type: number }
        speed_kmh: { type: integer }
        ignition: { type: boolean }
        movement_status: { type: integer }
        odometer: { type: integer }
        fuel_level: { type: integer }
        engine_rpm: { type: integer }
        battery_level: { type: integer }
        trip_uuid: { type: string }
        last_movement_time: { type: string, format: date-time, nullable: true }
        recorded_at: { type: string, format: date-time }

    Geofence:
      type: object
      properties:
        uuid: { type: string }
        name: { type: string }
        purpose: { type: string }
        geofence_type: { type: string }
        description: { type: string, nullable: true }
        radius_meters: { type: integer, nullable: true }
        speed_limit: { type: integer, nullable: true }
        area:
          description: GeoJSON geometry
          type: object
        is_global: { type: boolean }

    NotificationVehicle:
      type: object
      properties:
        uuid: { type: string }
        license_plate_number: { type: string }
        license_plate_category: { type: string }
        license_plate_state_code: { type: string }
        vehicle_type: { type: string }

    NotificationDriver:
      type: object
      properties:
        uuid: { type: string }
        first_name: { type: string }
        last_name: { type: string }

    Notification:
      type: object
      properties:
        uuid: { type: string }
        title: { type: string }
        message: { type: string }
        category: { type: string, example: transport }
        notification_type: { type: string }
        priority: { type: string }
        is_read: { type: boolean }
        read_at: { type: string, format: date-time, nullable: true }
        created_at: { type: string, format: date-time }
        device_created_at: { type: string, format: date-time, nullable: true }
        reference_type: { type: string, nullable: true }
        reference_uuid: { type: string, nullable: true }
        vehicle_uuid: { type: string, nullable: true }
        vehicle: { $ref: "#/components/schemas/NotificationVehicle" }
        driver_uuid: { type: string, nullable: true }
        driver: { $ref: "#/components/schemas/NotificationDriver" }
        trip_uuid: { type: string, nullable: true }
        latitude: { type: number, nullable: true }
        longitude: { type: number, nullable: true }
        violation_value: { type: string, nullable: true }
        total_duration_seconds: { type: integer, nullable: true }

    FleetTotals:
      type: object
      properties:
        total_vehicles: { type: integer }
        total_drivers: { type: integer }
        total_users: { type: integer }
        total_trips: { type: integer }

    FleetLiveStatus:
      type: object
      properties:
        transport_company_uuid: { type: string }
        total_vehicles: { type: integer }
        total_running_vehicles: { type: integer }
        total_idle_vehicles: { type: integer }
        total_offline_vehicles: { type: integer }
        total_inactive_vehicles: { type: integer }
        total_no_location_vehicles: { type: integer }
        total_active_vehicles: { type: integer }
        active_vehicles: { type: integer }
        maintenance_vehicles: { type: integer }
        inactive_status: { type: integer }

    VehicleTodayStats:
      type: object
      properties:
        total_distance_traveled: { type: number, description: Kilometers }
        total_fuel_consumption: { type: number, description: Liters }
        total_duration: { type: number, description: Seconds }
        total_trips: { type: integer }
        total_idle_duration: { type: integer, description: Seconds }

    VehicleTripCounts:
      type: object
      properties:
        trips_today: { type: integer }
        trips_last_7_days: { type: integer }
        trips_last_30_days: { type: integer }
        trips_last_90_days: { type: integer }

    VehicleHoursDriven:
      type: object
      properties:
        hours_today: { type: number }
        hours_last_7_days: { type: number }
        hours_last_30_days: { type: number }
        hours_last_90_days: { type: number }

    DistanceByMonth:
      type: object
      properties:
        month_year: { type: string, example: "2026-07" }
        total_distance_traveled: { type: number, description: Kilometers }

    FuelByMonth:
      type: object
      properties:
        month_year: { type: string, example: "2026-07" }
        total_fuel_consumption: { type: number, description: Liters }

    CreateFuelEntryRequest:
      type: object
      required: [vehicle_uuid, entry_date, quantity_liters]
      properties:
        vehicle_uuid: { type: string }
        entry_date: { type: string, example: "2026-08-17" }
        odometer_reading: { type: number, description: Must exceed the vehicle's previous entry }
        quantity_liters: { type: number, minimum: 0, exclusiveMinimum: true }
        unit_cost: { type: number, minimum: 0 }
        total_cost: { type: number }
        fuel_type: { type: string, default: diesel }
        station_name: { type: string }

    FuelEntry:
      type: object
      properties:
        uuid: { type: string }
        vehicle_uuid: { type: string }
        entry_date: { type: string }
        odometer_reading: { type: number }
        fuel_type: { type: string }
        quantity_liters: { type: number }
        unit_cost: { type: number }
        total_cost: { type: number }
        station_name: { type: string }
        station_location: { type: string }
        receipt_file_url: { type: string }
        fuel_efficiency_kpl: { type: number }
        created_at: { type: string, format: date-time }

    CreateJobCardRequest:
      type: object
      required: [vehicle_uuid, service_type_uuid]
      properties:
        vehicle_uuid: { type: string }
        service_type_uuid: { type: string }
        description: { type: string }
        priority: { type: string, enum: [low, medium, high, urgent], default: medium }
        scheduled_start_date: { type: string, example: "2026-08-20" }

    UpdateJobCardRequest:
      type: object
      properties:
        status: { type: string, enum: [in_progress, completed, cancelled] }
        description: { type: string }
        notes: { type: string }

    CreatePartRequest:
      type: object
      required: [part_number, name]
      properties:
        part_number: { type: string }
        name: { type: string }
        description: { type: string }
        category: { type: string }
        supplier_name: { type: string }
        unit_price: { type: number, minimum: 0 }
        currency: { type: string, default: AED }
        current_stock: { type: integer, minimum: 0 }
        min_stock_level: { type: integer }
        max_stock_level: { type: integer }
        unit_of_measure: { type: string }
        is_consumable: { type: boolean }

    UpdatePartRequest:
      type: object
      properties:
        name: { type: string }
        description: { type: string }
        category: { type: string }
        unit_price: { type: number, minimum: 0 }
        current_stock: { type: integer, minimum: 0 }
        min_stock_level: { type: integer }
        max_stock_level: { type: integer }
        unit_of_measure: { type: string }
        is_active: { type: boolean }
        is_consumable: { type: boolean }

    FuelTransaction:
      type: object
      properties:
        uuid: { type: string }
        vehicle_uuid: { type: string, nullable: true }
        driver_uuid: { type: string, nullable: true }
        fuel_card_uuid: { type: string, nullable: true }
        transaction_date: { type: string }
        quantity_liters: { type: number }
        unit_price: { type: number }
        total_amount: { type: number }
        currency: { type: string, example: AED }
        fuel_type: { type: string, example: diesel }
        station_name: { type: string, nullable: true }
        odometer_at_fill: { type: number, nullable: true }
        is_full_tank: { type: boolean }
        is_flagged: { type: boolean, description: Flagged by fraud detection }
        source: { type: string, nullable: true, example: card_import }
        created_at: { type: string, format: date-time }

    MaintenanceSchedule:
      type: object
      properties:
        uuid: { type: string }
        asset_type: { type: string, enum: [vehicle, machine] }
        vehicle_uuid: { type: string, nullable: true }
        machine_uuid: { type: string, nullable: true }
        asset_identifier:
          type: string
          nullable: true
          description: License plate (vehicles) or machine code (machines)
        service_type: { type: string }
        interval_km: { type: integer, nullable: true }
        interval_hours: { type: integer, nullable: true }
        interval_days: { type: integer, nullable: true }
        last_service_km: { type: integer, nullable: true }
        last_service_hours: { type: integer, nullable: true }
        last_service_date: { type: string, format: date, nullable: true }
        next_due_km: { type: integer, nullable: true }
        next_due_hours: { type: integer, nullable: true }
        next_due_date: { type: string, format: date, nullable: true }
        days_until_due: { type: integer, nullable: true }
        current_km: { type: integer, nullable: true }
        current_hours: { type: integer, nullable: true }
        urgency_status: { type: string, enum: [normal, due, overdue] }
        is_active: { type: boolean }
        created_at: { type: string, format: date-time }

    JobCard:
      type: object
      properties:
        uuid: { type: string }
        job_number: { type: string }
        status:
          type: string
          enum: [pending, assigned, in_progress, completed, approved, rejected, cancelled]
        priority: { type: string, enum: [low, medium, high, urgent] }
        asset_type: { type: string, enum: [vehicle, machine] }
        vehicle_uuid: { type: string, nullable: true }
        machine_uuid: { type: string, nullable: true }
        asset_identifier:
          type: string
          nullable: true
          description: License plate (vehicles) or machine code (machines)
        service_type: { type: string }
        description: { type: string, nullable: true }
        scheduled_start_date: { type: string, format: date, nullable: true }
        actual_start_date: { type: string, format: date-time, nullable: true }
        actual_end_date: { type: string, format: date-time, nullable: true }
        estimated_hours: { type: number, nullable: true }
        actual_hours: { type: number, nullable: true }
        estimated_cost: { type: number, nullable: true }
        actual_cost: { type: number, nullable: true }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    Part:
      type: object
      properties:
        uuid: { type: string }
        part_number: { type: string }
        name: { type: string }
        description: { type: string, nullable: true }
        category: { type: string, nullable: true }
        supplier_name: { type: string, nullable: true }
        unit_price: { type: number }
        currency: { type: string, nullable: true }
        current_stock: { type: integer }
        min_stock_level: { type: integer }
        max_stock_level: { type: integer }
        unit_of_measure: { type: string, nullable: true }
        warranty_months: { type: integer, nullable: true }
        is_active: { type: boolean }
        is_consumable: { type: boolean }
        stock_status: { type: string, enum: [normal, low_stock, out_of_stock, overstock] }
        created_at: { type: string, format: date-time }

    StockAlert:
      type: object
      properties:
        uuid: { type: string }
        part_uuid: { type: string }
        part_name: { type: string }
        part_number: { type: string }
        alert_type: { type: string, enum: [low_stock, out_of_stock, overstock] }
        current_stock: { type: integer }
        min_stock_level: { type: integer }
        status: { type: string, enum: [active, acknowledged, resolved] }
        created_at: { type: string, format: date-time }

    IsoLink:
      type: object
      properties:
        Rel: { type: string, enum: [Self, Next] }
        Href: { type: string }

    IsoEquipmentHeader:
      type: object
      properties:
        OEMName: { type: string }
        Model: { type: string }
        EquipmentID: { type: string, description: "Vehicle uuid." }
        SerialNumber: { type: string, description: "VIN/chassis number, or the uuid when no VIN is recorded." }
        PIN: { type: string, description: "VIN. Omitted when not recorded." }

    IsoLocation:
      type: object
      properties:
        Latitude: { type: number }
        Longitude: { type: number }
        Datetime: { type: string, format: date-time }

    IsoCumulativeOperatingHours:
      type: object
      properties:
        Hour: { type: number }
        Datetime: { type: string, format: date-time }

    IsoDistance:
      type: object
      properties:
        Odometer: { type: number }
        OdometerUnits: { type: string, enum: [kilometre] }
        Datetime: { type: string, format: date-time }

    IsoFuelRemaining:
      type: object
      properties:
        Percent: { type: number }

    IsoFaultCode:
      type: object
      properties:
        CodeIdentifier: { type: string }
        CodeDescription: { type: string }
        CodeSeverity: { type: string, enum: [info, warning, critical, emergency] }
        Datetime: { type: string, format: date-time }

    IsoEquipment:
      type: object
      description: Blocks with no underlying data are omitted, never zero-filled.
      properties:
        EquipmentHeader: { $ref: "#/components/schemas/IsoEquipmentHeader" }
        Location: { $ref: "#/components/schemas/IsoLocation" }
        CumulativeOperatingHours: { $ref: "#/components/schemas/IsoCumulativeOperatingHours" }
        Distance: { $ref: "#/components/schemas/IsoDistance" }
        FuelRemaining: { $ref: "#/components/schemas/IsoFuelRemaining" }
        FaultCodes:
          type: array
          items: { $ref: "#/components/schemas/IsoFaultCode" }

    IsoFleet:
      type: object
      properties:
        Links:
          type: array
          items: { $ref: "#/components/schemas/IsoLink" }
        Equipment:
          type: array
          items: { $ref: "#/components/schemas/IsoEquipment" }

    IsoFaultCodeSeries:
      type: object
      properties:
        Links:
          type: array
          items: { $ref: "#/components/schemas/IsoLink" }
        FaultCodes:
          type: array
          items: { $ref: "#/components/schemas/IsoFaultCode" }

    IsoLocationSeries:
      type: object
      properties:
        Links:
          type: array
          items: { $ref: "#/components/schemas/IsoLink" }
        Locations:
          type: array
          items: { $ref: "#/components/schemas/IsoLocation" }
