openapi: 3.1.0
info:
  title: BetterFleet Operations API
  version: 0.4.0-pre
  summary: Draft OpenAPI contract for operational integrations covering dispatch, charging, readiness, yard state, telemetry, and integration operations.
  description: |
    Draft Operations API contract for BetterFleet operational integrations, based on the Section E - API and Integration requirement set.

    This contract is intended as an indicative pre-release API surface for design review. It aligns with BetterFleet's current public API direction:

    - REST endpoints under the pre-release public API namespace.
    - JSON request and response bodies using snake_case field names.
    - UUID strings for BetterFleet entity identifiers.
    - ISO 8601 timestamps. Request timestamps should include a timezone, preferably UTC with a Z suffix. Response timestamps are UTC.
    - Additive evolution for contract changes. Breaking changes should be versioned.
    - Event push is preferred for operational changes; polling endpoints are still provided for snapshot reads and backfill.

    ### Conceptual alignment with VDV 463

    - The API models the same operational exchange between upstream operational systems and a charging/load-management layer: bootstrapped secure access, charging demand/priority, charging and charger state, vehicle state, depot context, and status updates.
    - ChargingPriorityQueue provides BetterFleet's REST read-model analogue for charging request and target information such as priority, required-by time, target SoC, expected arrival time, and expected arrival SoC.
    - ChargerStatusSnapshot, ActiveSessionsSnapshot, VehicleTelemetrySnapshot, ReadinessAssessmentCollection, YardStateSnapshot, and webhook events provide BetterFleet's REST/webhook analogue for charging information published by the charging management side.
    - This API deliberately keeps BetterFleet REST resource names, snake_case fields, OAuth/API-key authentication options, and webhook delivery. It does not expose the VDV 463 WebSocket envelope, VDV field names, or VDV message names.

    Several operations in this draft depend on implementation scope and upstream data availability. Fields marked as nullable may be unavailable when a charger, telematics provider, BMS/OEM feed, or dispatch input has not supplied the required data.
  contact:
    name: BetterFleet
servers:
  - url: https://api.betterfleet.example
    description: Production API base URL. Final hostname to be confirmed.
  - url: https://staging-api.betterfleet.example
    description: Staging and integration validation API base URL. Final hostname to be confirmed.
tags:
  - name: Authentication
    description: Token exchange and API security mechanisms.
  - name: Readiness
    description: Vehicle readiness assessments, viability, predictive readiness, and block feasibility.
  - name: Vehicle Telemetry
    description: Vehicle telemetry snapshots including SoC, source, location, speed, and stale-data state.
  - name: Yard State
    description: Depot space occupancy, vehicle yard position, and physical accessibility context.
  - name: Range Prediction
    description: Vehicle range prediction with optional block-level feasibility output.
  - name: Charging
    description: Charger, connector, active session, OCPP status, and charging queue APIs.
  - name: Dispatch
    description: Dispatch recommendations and charge priority data derived from dispatch and charge-plan inputs.
  - name: Webhooks
    description: Event push subscription management and event payloads.
  - name: Integration Operations
    description: Rate limits, throughput guidance, environments, and API documentation discovery.
security:
  - CognitoBearerJwt: []
  - ApiKeyAuth: []
paths:
  /pre/depots/{depot_id}/readiness-assessments:
    get:
      tags: [Readiness]
      summary: List readiness assessments for a depot
      description: |
        Returns a depot-level readiness assessment collection for a service date and assessment time. The readiness status uses BetterFleet dispatch viability values: viable, potentially_viable, and not_viable.

        service_date is the depot-local dispatch service date used for duty and block lookup. assessment_time is the operational clock instant used for SoC, charging, yard, and prediction state. Omit assessment_time to assess current readiness at request time, equivalent to "now". Supply a future assessment_time to ask BetterFleet for predictive readiness at that operational time.

        If assessment_time is supplied, it should be associated with the requested service_date in depot-local operating terms. Cross-midnight duties should still use the dispatch service_date that owns the block. Values that conflict with the depot's service-date rules should return 422 unless a project-specific integration design defines another rule.

        Future assessments combine latest SoC, predicted energy required, next assignment, time to departure, charge-plan data, yard accessibility where available, and confidence.

        The response wrapper, ReadinessAssessmentCollection, describes the query context and assessment clock. Each VehicleReadinessAssessment describes one vehicle, optionally scoped to one dispatch block, with the status, reason codes, SoC evidence, departure needs, predicted SoC, yard context, confidence, and last update time.

        Recommended update pattern: subscribe to vehicle.readiness_changed events for changes, and poll this endpoint every 30 to 60 seconds only when an operational screen or integration process needs a full snapshot.

        GET   /pre/depots/{depot_id}/readiness-assessments
      operationId: listReadinessAssessments
      x-betterfleet-requirement: E.1 / E.9
      parameters:
        - $ref: "#/components/parameters/depot_id"
        - $ref: "#/components/parameters/service_date"
        - $ref: "#/components/parameters/assessment_time_optional"
        - $ref: "#/components/parameters/block_id_optional"
        - $ref: "#/components/parameters/vehicle_id_optional"
      responses:
        "200":
          description: Readiness assessment collection for vehicles matching the query.
          headers:
            X-Recommended-Poll-Interval-Seconds:
              schema:
                type: integer
                example: 60
              description: Suggested polling interval when webhook push is not used.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReadinessAssessmentCollection"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"

  /pre/vehicles/{vehicle_id}/telemetry:
    get:
      tags: [Vehicle Telemetry]
      summary: Get vehicle telemetry snapshot
      description: |
        Returns the latest telemetry snapshot known to BetterFleet for one vehicle, including per-field observations for SoC, location, speed, odometer, source, observed timestamp, stale-data state, and optional charging context.

        Plugged-in SoC normally comes from chargepoint, charging-session, or OCPP-derived telemetry. In-motion SoC, location, speed, and odometer data require a vehicle-side source such as telematics, BMS, or OEM API data. BetterFleet resolves these source-specific observations into one latest-known vehicle telemetry snapshot and exposes each field's source and observed timestamp.

        GET   /pre/vehicles/{vehicle_id}/telemetry
      operationId: getVehicleTelemetrySnapshot
      x-betterfleet-requirement: E.2 / E.8
      parameters:
        - $ref: "#/components/parameters/vehicle_id"
      responses:
        "200":
          description: Latest telemetry snapshot for the vehicle.
          headers:
            X-Recommended-Poll-Interval-Seconds:
              schema:
                type: integer
                example: 60
              description: Suggested polling interval. Align lower values to the upstream source update cadence.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VehicleTelemetrySnapshot"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"

  /pre/depots/{depot_id}/vehicle-telemetry:
    get:
      tags: [Vehicle Telemetry]
      summary: List vehicle telemetry snapshots for a depot
      description: |
        Returns the latest known telemetry snapshots for vehicles in a depot. This collection endpoint is intended for operational screens, dispatch integrations, and fleet-wide sync jobs so clients do not need to issue one request per vehicle.

        Omit vehicle_ids to return all vehicles in the depot. Use the filter when an integration needs a bounded subset.

        GET   /pre/depots/{depot_id}/vehicle-telemetry
      operationId: listDepotVehicleTelemetrySnapshots
      x-betterfleet-requirement: E.2 / E.8
      parameters:
        - $ref: "#/components/parameters/depot_id"
        - $ref: "#/components/parameters/vehicle_ids_optional"
      responses:
        "200":
          description: Latest vehicle telemetry snapshots for the depot.
          headers:
            X-Recommended-Poll-Interval-Seconds:
              schema:
                type: integer
                example: 60
              description: Suggested polling interval when webhook push is not used.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VehicleTelemetrySnapshotCollection"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"

  /pre/vehicles/{vehicle_id}/range-prediction:
    post:
      tags: [Range Prediction]
      summary: Predict vehicle range and optional block feasibility
      description: |
        Predicts available vehicle range from BetterFleet's internal vehicle, SoC, location, weather, and vehicle-model data. The standard external input is the vehicle_id path parameter. When a dispatch block can be inferred or a block_id override is supplied, the response also includes block-level feasibility output.

        ### Range prediction input model

        For the standard Operations API contract, the integration sends the vehicle ID in the path. BetterFleet uses that identifier to derive the remaining prediction inputs from internal systems, instead of asking the caller to rebuild the Digital Twin payload.

        The derived data includes latest SoC, depot and location context, active or next dispatch block where available, vehicle model, battery capacity and health, vehicle mass, passenger or load assumptions, HVAC and drivetrain parameters, weather for the prediction window, reserve SoC, and minimum safe SoC.

        The OpenAPI request body documents optional scenario overrides that map to the internal RangeRequestOverride-style data: location, block, range unit, passenger count, prediction window, expected distance rate, auxiliary energy per km, reserve/current/minimum SoC, and vehicle details. Clients can omit the body for normal vehicle-id based predictions.

        Use POST because callers may provide optional override inputs for what-if checks, but no request body is required for the standard vehicle-id based prediction.
      operationId: createRangePrediction
      x-betterfleet-requirement: E.3
      parameters:
        - $ref: "#/components/parameters/vehicle_id"
      requestBody:
        required: false
        description: Optional scenario override data. Omit for the standard prediction where BetterFleet derives the input data from internal systems.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RangePredictionRequest"
      responses:
        "200":
          description: Range prediction with optional block feasibility.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RangePredictionResponse"
        "422":
          $ref: "#/components/responses/ValidationError"
        "429":
          $ref: "#/components/responses/RateLimited"

  /pre/depots/{depot_id}/charger-status:
    get:
      tags: [Charging]
      summary: List charger and connector status for a depot
      description: |
        Returns current charger and connector status for a depot. This should be used for operational snapshots rather than historical reporting.

        Polling guidance: 30 to 60 seconds for dashboards. Event push is preferred for fault, online/offline, and active charging changes.
      operationId: listDepotChargerStatus
      x-betterfleet-requirement: E.4
      parameters:
        - $ref: "#/components/parameters/depot_id"
        - $ref: "#/components/parameters/charger_id_optional"
      responses:
        "200":
          description: Charger and connector status snapshot.
          headers:
            X-Recommended-Poll-Interval-Seconds:
              schema:
                type: integer
                example: 60
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChargerStatusSnapshot"
        "429":
          $ref: "#/components/responses/RateLimited"

  /pre/depots/{depot_id}/active-sessions:
    get:
      tags: [Charging]
      summary: List active charging session pairings for a depot
      description: |
        Returns active charging session to vehicle pairings, including charger, connector, vehicle id tag, normalized session state, and OCPP-compatible connector status.

        Latency depends on charger/OCPP message arrival and BetterFleet processing. Event push is preferred for session start, active charging, paused, faulted, and stopped changes.
      operationId: listActiveSessionPairings
      x-betterfleet-requirement: E.5
      parameters:
        - $ref: "#/components/parameters/depot_id"
      responses:
        "200":
          description: Active session pairings.
          headers:
            X-Recommended-Poll-Interval-Seconds:
              schema:
                type: integer
                example: 30
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ActiveSessionsSnapshot"
        "429":
          $ref: "#/components/responses/RateLimited"

  /pre/charging-sessions/{session_id}:
    get:
      tags: [Charging]
      summary: Get one charging session pairing
      operationId: getChargingSessionPairing
      x-betterfleet-requirement: E.5
      parameters:
        - $ref: "#/components/parameters/session_id"
      responses:
        "200":
          description: Charging session pairing.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ActiveSessionPairing"
        "404":
          $ref: "#/components/responses/NotFound"

  /pre/depots/{depot_id}/charging-priority-queue:
    get:
      tags: [Dispatch]
      summary: Get charging priority queue for a dispatch date
      description: |
        Returns a ranked charging queue derived from dispatch readiness, SoC urgency, assigned duties, target SoC, departure times, and charge-plan preview data.

        This endpoint maps closely to BetterFleet charge-plan preview candidates. It is intended as the integration-facing queue, not as a command to send charge requests to downstream systems.
      operationId: getChargingPriorityQueue
      x-betterfleet-requirement: E.7
      parameters:
        - $ref: "#/components/parameters/depot_id"
        - $ref: "#/components/parameters/dispatch_date"
      responses:
        "200":
          description: Ranked charge queue.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChargingPriorityQueue"
        "429":
          $ref: "#/components/responses/RateLimited"

  /pre/depots/{depot_id}/dispatch-recommendations:
    get:
      tags: [Dispatch]
      summary: Get composite dispatch recommendations
      description: |
        Returns BetterFleet dispatch recommendations based on the EV energy intelligence layer: SoC, charging state, predicted range, block feasibility, charge priority, depot blocking, and readiness risk.

        External operational systems can combine these outputs with parking, yard, operator, service plan, and dispatch rules.
      operationId: getDispatchRecommendations
      x-betterfleet-requirement: E.10
      parameters:
        - $ref: "#/components/parameters/depot_id"
        - $ref: "#/components/parameters/service_date"
      responses:
        "200":
          description: Composite dispatch recommendations.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DispatchRecommendationSet"
        "429":
          $ref: "#/components/responses/RateLimited"

  /pre/depots/{depot_id}/yard-state:
    get:
      tags: [Yard State]
      summary: Get yard state for a depot
      description: |
        Returns a snapshot of depot space occupancy, vehicle yard position, and physical accessibility context.

        GET   /pre/depots/{depot_id}/yard-state

        This endpoint aligns the existing Yard State API with the Operations API conventions: depot-scoped resources, UUID depot identifiers, snapshot metadata, stale-data signalling, rate-limit headers, and event push for state changes.
      operationId: getDepotYardState
      x-betterfleet-requirement: Yard State alignment
      parameters:
        - $ref: "#/components/parameters/depot_id"
      responses:
        "200":
          description: Yard state snapshot for the depot.
          headers:
            X-Recommended-Poll-Interval-Seconds:
              schema:
                type: integer
                example: 60
              description: Suggested polling interval when webhook push is not used.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/YardStateSnapshot"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"

  /pre/depots/{depot_id}/yard-spaces:
    patch:
      tags: [Yard State]
      summary: Update multiple yard spaces for a depot
      description: |
        Applies a batch of space state observations and returns the resulting depot yard state snapshot.

        Updates should include source observation timestamps so BetterFleet can reject, ignore, or flag stale yard telemetry rather than overwriting newer state with older observations.
      operationId: updateDepotYardSpaces
      x-betterfleet-requirement: Yard State alignment
      parameters:
        - $ref: "#/components/parameters/depot_id"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/YardSpaceBulkUpdateRequest"
      responses:
        "200":
          description: Updated yard state snapshot.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/YardStateSnapshot"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/ValidationError"
        "429":
          $ref: "#/components/responses/RateLimited"

  /pre/depots/{depot_id}/yard-spaces/{space_id}:
    get:
      tags: [Yard State]
      summary: Get one yard space
      description: Returns the latest known occupancy, occupant, and position context for one depot space.
      operationId: getYardSpace
      x-betterfleet-requirement: Yard State alignment
      parameters:
        - $ref: "#/components/parameters/depot_id"
        - $ref: "#/components/parameters/space_id"
      responses:
        "200":
          description: Yard space state.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/YardSpace"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
    patch:
      tags: [Yard State]
      summary: Update one yard space
      description: |
        Updates the latest known occupancy, occupant, and position context for one depot space.

        PATCH /pre/depots/{depot_id}/yard-spaces/{space_id}
      operationId: updateYardSpace
      x-betterfleet-requirement: Yard State alignment
      parameters:
        - $ref: "#/components/parameters/depot_id"
        - $ref: "#/components/parameters/space_id"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/YardSpaceUpdate"
      responses:
        "200":
          description: Updated yard space state.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/YardSpace"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/ValidationError"
        "429":
          $ref: "#/components/responses/RateLimited"

  /pre/webhook-subscriptions:
    get:
      tags: [Webhooks]
      summary: List webhook subscriptions
      description: Lists configured webhook subscriptions for the authenticated integration principal.
      operationId: listWebhookSubscriptions
      x-betterfleet-requirement: E.13
      responses:
        "200":
          description: Webhook subscriptions.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/WebhookSubscription"
    post:
      tags: [Webhooks]
      summary: Create webhook subscription
      description: |
        Creates a webhook subscription for selected event types. BetterFleet should sign outbound requests and retry failed deliveries with idempotent event_id values.
      operationId: createWebhookSubscription
      x-betterfleet-requirement: E.13
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookSubscriptionRequest"
      responses:
        "201":
          description: Webhook subscription created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookSubscription"
        "422":
          $ref: "#/components/responses/ValidationError"

  /pre/webhook-subscriptions/{subscription_id}:
    delete:
      tags: [Webhooks]
      summary: Delete webhook subscription
      operationId: deleteWebhookSubscription
      x-betterfleet-requirement: E.13
      parameters:
        - $ref: "#/components/parameters/subscription_id"
      responses:
        "204":
          description: Webhook subscription deleted.
        "404":
          $ref: "#/components/responses/NotFound"

  /pre/integration/rate-limits:
    get:
      tags: [Integration Operations]
      summary: Get integration rate limit and polling guidance
      description: |
        Returns the agreed rate limit and throughput guidance for the integration principal. Values in this draft are recommended starting points and should be confirmed during integration design.
      operationId: getIntegrationRateLimits
      x-betterfleet-requirement: E.12
      responses:
        "200":
          description: Rate limits and recommended polling intervals.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RateLimitPolicy"

  /pre/integration/environments:
    get:
      tags: [Integration Operations]
      summary: List integration environments
      description: |
        Describes available non-production and production environments for integration validation. Staging is the recommended environment for integration testing before production deployment.
      operationId: listIntegrationEnvironments
      x-betterfleet-requirement: E.14
      responses:
        "200":
          description: Available integration environments.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IntegrationEnvironments"

  /api-versions:
    get:
      tags: [Integration Operations]
      summary: List public API versions
      description: Mirrors BetterFleet's current public API version discovery endpoint.
      operationId: listApiVersions
      x-betterfleet-requirement: E.14
      responses:
        "200":
          description: Available public API versions.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/PublicApiVersion"

  /{version}/openapi.json:
    get:
      tags: [Integration Operations]
      summary: Get OpenAPI schema for an API version
      description: Mirrors BetterFleet's current OpenAPI schema discovery endpoint.
      operationId: getOpenApiSchema
      x-betterfleet-requirement: E.14
      parameters:
        - name: version
          in: path
          required: true
          schema:
            type: string
            enum: [pre, all]
          description: Public API version identifier.
      responses:
        "200":
          description: OpenAPI schema for the requested API version.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true

webhooks:
  chargingSessionEvent:
    post:
      tags: [Webhooks]
      summary: Charging session event delivery
      description: |
        BetterFleet sends this payload to the subscriber's webhook URL when a charging session starts, becomes active, pauses, faults, or stops.
      operationId: receiveChargingSessionEvent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChargingSessionEvent"
      responses:
        "202":
          description: Event accepted by the subscriber.
  vehicleReadinessChangedEvent:
    post:
      tags: [Webhooks]
      summary: Vehicle readiness changed event delivery
      description: BetterFleet sends this payload when a vehicle readiness flag changes.
      operationId: receiveVehicleReadinessChangedEvent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/VehicleReadinessChangedEvent"
      responses:
        "202":
          description: Event accepted by the subscriber.
  vehicleTelemetryChangedEvent:
    post:
      tags: [Webhooks]
      summary: Vehicle telemetry changed event delivery
      description: BetterFleet sends this payload when vehicle telemetry changes or becomes stale.
      operationId: receiveVehicleTelemetryChangedEvent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/VehicleTelemetryChangedEvent"
      responses:
        "202":
          description: Event accepted by the subscriber.
  chargerStatusChangedEvent:
    post:
      tags: [Webhooks]
      summary: Charger status changed event delivery
      operationId: receiveChargerStatusChangedEvent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChargerStatusChangedEvent"
      responses:
        "202":
          description: Event accepted by the subscriber.
  yardSpaceStateChangedEvent:
    post:
      tags: [Webhooks]
      summary: Yard space state changed event delivery
      description: BetterFleet sends this payload when a depot space occupancy state changes.
      operationId: receiveYardSpaceStateChangedEvent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/YardSpaceStateChangedEvent"
      responses:
        "202":
          description: Event accepted by the subscriber.
  yardOccupantChangedEvent:
    post:
      tags: [Webhooks]
      summary: Yard occupant changed event delivery
      description: BetterFleet sends this payload when the vehicle assigned to a depot space changes.
      operationId: receiveYardOccupantChangedEvent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/YardOccupantChangedEvent"
      responses:
        "202":
          description: Event accepted by the subscriber.

components:
  securitySchemes:
    CognitoBearerJwt:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: Authenticate with a BetterFleet Cognito bearer JWT.
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: API key authentication for selected integration workflows where enabled.
    MutualTls:
      type: mutualTLS
      description: Roadmap-capable mTLS scheme. Use only when the agreed security design requires client certificate authentication.

  parameters:
    depot_id:
      name: depot_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
      description: BetterFleet depot UUID.
    vehicle_id:
      name: vehicle_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
      description: BetterFleet vehicle UUID.
    vehicle_id_optional:
      name: vehicle_id
      in: query
      required: false
      schema:
        type: string
        format: uuid
      description: Optional vehicle UUID filter.
    vehicle_ids_optional:
      name: vehicle_ids
      in: query
      required: false
      style: form
      explode: false
      schema:
        type: array
        items:
          type: string
          format: uuid
      description: Optional comma-separated vehicle UUID filters. Omit to return all vehicles in the depot.
    charger_id_optional:
      name: charger_id
      in: query
      required: false
      schema:
        type: string
        format: uuid
      description: Optional charger UUID filter. Omit to return all chargers in the depot.
    session_id:
      name: session_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
      description: BetterFleet charging session or transaction UUID.
    subscription_id:
      name: subscription_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
      description: Webhook subscription UUID.
    space_id:
      name: space_id
      in: path
      required: true
      schema:
        type: string
      description: Canonical depot space identifier, such as a parking bay, lane position, or charger-adjacent stand.
    service_date:
      name: service_date
      in: query
      required: true
      schema:
        type: string
        format: date
        example: "2027-01-12"
      description: Dispatch service date in depot-local operating terms. This scopes duty and block lookup and remains authoritative for cross-midnight duties.
    assessment_time_optional:
      name: assessment_time
      in: query
      required: false
      schema:
        type: string
        format: date-time
        example: "2027-01-12T08:30:00Z"
      description: Operational time at which readiness should be assessed. Omit to assess current readiness at request time. Supply a future timestamp for predictive readiness. When supplied, the timestamp should be associated with the requested service_date under the depot's operating-day rules.
    dispatch_date:
      name: dispatch_date
      in: query
      required: true
      schema:
        type: string
        format: date
        example: "2027-01-12"
      description: Dispatch date for charge-plan and priority queue calculations.
    block_id_optional:
      name: block_id
      in: query
      required: false
      schema:
        type: string
        format: uuid
      description: Optional dispatch block UUID.

  responses:
    Unauthorized:
      description: Missing, expired, or invalid authentication credential.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Forbidden:
      description: Authenticated principal does not have access to the requested resource.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    NotFound:
      description: Requested resource was not found.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    ValidationError:
      description: Request validation failed.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/HttpValidationError"
    RateLimited:
      description: Request was throttled because the client exceeded its agreed rate limit.
      headers:
        Retry-After:
          schema:
            type: integer
          description: Seconds to wait before retrying.
        X-RateLimit-Limit:
          schema:
            type: integer
          description: Maximum requests allowed in the current window.
        X-RateLimit-Remaining:
          schema:
            type: integer
          description: Requests remaining in the current window.
        X-RateLimit-Reset:
          schema:
            type: string
            format: date-time
          description: UTC timestamp when the current window resets.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"

  schemas:
    ErrorResponse:
      type: object
      required: [detail]
      properties:
        detail:
          type: string
          description: Human-readable error detail.

    HttpValidationError:
      type: object
      properties:
        detail:
          type: array
          items:
            type: object
            required: [loc, msg, type]
            properties:
              loc:
                type: array
                items:
                  oneOf:
                    - type: string
                    - type: integer
              msg:
                type: string
              type:
                type: string

    ReadinessStatus:
      type: string
      enum: [viable, potentially_viable, not_viable]
      description: Viability status for a vehicle or vehicle/block pairing at the requested assessment time.

    ReadinessReasonCode:
      type: string
      enum:
        - ALREADY_ALLOCATED
        - OVERLAPPING
        - MINIMAL_TIME_IN_DEPOT
        - UNABLE_TO_DETERMINE_BLOCK_ENERGY
        - INSUFFICIENT_ENERGY
        - STALE_ENERGY
        - VEHICLE_MISSING_MODEL
        - VEHICLE_MISSING_STATUS
        - VEHICLE_MISSING_DETAILS
        - VEHICLE_MISSING_SOC
        - PHYSICALLY_BLOCKED
        - EXTERNAL
      description: |
        Reason code explaining a potentially_viable or not_viable readiness result.

        | Code | Meaning |
        | --- | --- |
        | ALREADY_ALLOCATED | Vehicle is already assigned to another block or operational commitment. |
        | OVERLAPPING | Candidate block overlaps an existing assignment, charge plan, or unavailable window. |
        | MINIMAL_TIME_IN_DEPOT | Vehicle has too little depot dwell time before departure to reach the readiness target with the available charging plan. |
        | UNABLE_TO_DETERMINE_BLOCK_ENERGY | BetterFleet could not derive the energy needed for the block from schedule, route, model, or fallback data. |
        | INSUFFICIENT_ENERGY | Latest or predicted SoC is below the required departure or safe-arrival threshold. |
        | STALE_ENERGY | SoC or energy evidence is older than the agreed freshness threshold. |
        | VEHICLE_MISSING_MODEL | Vehicle model, battery, efficiency, or drivetrain data needed for prediction is missing. |
        | VEHICLE_MISSING_STATUS | Vehicle operational state is missing or cannot be trusted. |
        | VEHICLE_MISSING_DETAILS | Required vehicle metadata is missing. |
        | VEHICLE_MISSING_SOC | BetterFleet has no usable current SoC for the vehicle. |
        | PHYSICALLY_BLOCKED | Yard or parking context indicates the vehicle cannot be reached or moved for dispatch. |
        | EXTERNAL | A configured external rule, source system, or manual override prevents a viable result. |

    Confidence:
      type: string
      enum: [high, medium, low]

    TelemetrySource:
      type: string
      enum: [chargepoint, charging_session, ocpp, telematics, bms, oem_api, derived, unknown]
      description: Source used for an operational telemetry observation. chargepoint, charging_session, and ocpp cover plugged-in charging infrastructure telemetry; telematics, bms, and oem_api cover vehicle-side telemetry.

    VehicleState:
      type: string
      enum: [Charging, OnRoute, InDepot, Maintenance]

    RangeUnit:
      type: string
      enum: [km, mi]
      description: Unit requested for range scenario calculations. Operations API responses keep predicted_range_km as the canonical metric output in this draft.

    BetterFleetChargerStatus:
      type: string
      enum: [available, charging, faulted, unavailable, reserved, occupied, unknown]
      description: BetterFleet-normalized charger-level operational status for dispatch and dashboard logic. This is not a raw OCPP status value.

    BetterFleetConnectorStatus:
      type: string
      enum: [available, preparing, charging, suspended_evse, suspended_ev, finishing, reserved, unavailable, faulted, occupied, unknown]
      description: BetterFleet-normalized connector-level operational status. occupied is an operational occupancy state derived from sessions, pairing, or local charger data, not an OCPP connector status.

    OcppConnectorStatus:
      type: string
      enum:
        - Available
        - Preparing
        - Charging
        - SuspendedEVSE
        - SuspendedEV
        - Finishing
        - Reserved
        - Unavailable
        - Faulted
      description: Raw OCPP connector StatusNotification status value where supplied by the charging infrastructure. This intentionally excludes BetterFleet-normalized states such as occupied.

    NormalizedSessionState:
      type: string
      enum:
        - plugged_in
        - preparing
        - actively_charging
        - suspended_ev
        - suspended_evse
        - finishing
        - faulted
        - completed
        - unknown
      description: BetterFleet normalized session state for integration logic.

    Location:
      type: object
      required: [latitude, longitude]
      properties:
        latitude:
          type: number
          format: double
        longitude:
          type: number
          format: double
        altitude_m:
          type: [number, "null"]
          description: Optional altitude above mean sea level in metres.

    YardSpaceState:
      type: string
      enum: [occupied, vacant, reserved, unavailable, unknown]
      description: Occupancy status of a depot space.

    YardVehicleOperationalState:
      type: string
      enum: [charging, ready, parked, waiting, unknown]
      description: Yard-source operational state for a vehicle while it is in a depot space.

    YardSpaceIndex:
      type: object
      properties:
        lane:
          type: [string, "null"]
          description: Lane identifier within the depot.
        position_index:
          type: [integer, "null"]
          description: Zero-based index representing the position within the lane.

    YardOccupant:
      type: object
      required: [vehicle_id, observed_at]
      properties:
        vehicle_id:
          type: string
          format: uuid
          description: BetterFleet vehicle occupying the space.
        vehicle_name:
          type: [string, "null"]
          description: Friendly vehicle name or fleet number.
        vehicle_state:
          oneOf:
            - $ref: "#/components/schemas/VehicleState"
            - type: "null"
        yard_operational_state:
          oneOf:
            - $ref: "#/components/schemas/YardVehicleOperationalState"
            - type: "null"
          description: Yard-source vehicle state such as parked, waiting, ready, or charging.
        observed_at:
          type: string
          format: date-time
          description: Source timestamp when the occupancy linkage was observed.
        heading_degrees:
          type: [number, "null"]
          minimum: 0
          maximum: 360
          description: Vehicle heading in degrees relative to true north.
        vehicle_location:
          oneOf:
            - $ref: "#/components/schemas/Location"
            - type: "null"
        source_system:
          type: [string, "null"]
          description: System that supplied the occupant observation.

    YardSpaceUpdate:
      type: object
      required: [space_id, state, observed_at]
      properties:
        space_id:
          type: string
          description: Canonical identifier for the depot space.
        state:
          $ref: "#/components/schemas/YardSpaceState"
        observed_at:
          type: string
          format: date-time
          description: Source timestamp when the space state was observed.
        occupant:
          oneOf:
            - $ref: "#/components/schemas/YardOccupant"
            - type: "null"
        source_system:
          type: [string, "null"]
          description: System that supplied the space state.

    YardSpace:
      allOf:
        - $ref: "#/components/schemas/YardSpaceUpdate"
        - type: object
          required: [depot_id, last_updated, is_stale]
          properties:
            depot_id:
              type: string
              format: uuid
            space_label:
              type: [string, "null"]
              description: Human-readable lane or position label.
            source_site_id:
              type: [string, "null"]
              description: Source-system site identifier when the upstream yard feed uses site ids instead of BetterFleet depot ids.
            source_site_label:
              type: [string, "null"]
              description: Source-system site label when supplied by the upstream yard feed.
            space_index:
              oneOf:
                - $ref: "#/components/schemas/YardSpaceIndex"
                - type: "null"
            space_location:
              oneOf:
                - $ref: "#/components/schemas/Location"
                - type: "null"
            last_updated:
              type: string
              format: date-time
              description: UTC time when BetterFleet last updated this yard-space read model.
            is_stale:
              type: boolean
              description: True when the latest source observation is older than the agreed freshness threshold.
            confidence:
              oneOf:
                - $ref: "#/components/schemas/Confidence"
                - type: "null"

    YardSpaceBulkUpdateRequest:
      type: object
      required: [updates]
      properties:
        updates:
          type: array
          items:
            $ref: "#/components/schemas/YardSpaceUpdate"

    YardStateSnapshot:
      type: object
      required: [depot_id, generated_at, refresh_after_seconds, spaces]
      properties:
        depot_id:
          type: string
          format: uuid
        generated_at:
          type: string
          format: date-time
        refresh_after_seconds:
          type: integer
          description: Suggested polling interval when webhook push is not used.
        spaces:
          type: array
          items:
            $ref: "#/components/schemas/YardSpace"

    YardContext:
      type: object
      required: [space_id, space_state, observed_at, is_stale]
      properties:
        space_id:
          type: string
        space_label:
          type: [string, "null"]
        space_state:
          $ref: "#/components/schemas/YardSpaceState"
        observed_at:
          type: string
          format: date-time
        is_stale:
          type: boolean
        source_system:
          type: [string, "null"]
        occupant_vehicle_id:
          type: [string, "null"]
          format: uuid

    ReadinessAssessmentCollection:
      type: object
      description: |
        Response wrapper for a depot readiness query. It records the depot, service date, response generation time, assessment time, recommended refresh interval, and the vehicle/block readiness results.

        generated_at is when BetterFleet built the API response. assessment_time is the operational time being assessed. If the caller omitted assessment_time, the value is the request-time "now" used for current readiness. If the caller supplied a future assessment_time, the assessment results are predictive. Each VehicleReadinessAssessment repeats assessment_time so the result remains self-contained when reused in events or block-feasibility payloads.
      required: [depot_id, service_date, generated_at, assessment_time, refresh_after_seconds, assessments]
      properties:
        depot_id:
          type: string
          format: uuid
        service_date:
          type: string
          format: date
        generated_at:
          type: string
          format: date-time
        assessment_time:
          type: string
          format: date-time
          description: Operational time used for the readiness calculation. Defaults to request time when omitted by the caller.
        refresh_after_seconds:
          type: integer
          example: 60
        assessments:
          type: array
          items:
            $ref: "#/components/schemas/VehicleReadinessAssessment"

    VehicleReadinessAssessment:
      type: object
      description: |
        One readiness result for a vehicle, optionally scoped to one dispatch block. The result contains the viability status, reason codes, SoC evidence, departure needs, predicted SoC, energy estimate, yard context, confidence, and last update time.

        This object is used for both current and predictive readiness. Current readiness is produced when assessment_time is the request-time "now"; predictive readiness is produced when assessment_time is in the future.
      required:
        - vehicle_id
        - depot_id
        - service_date
        - assessment_time
        - readiness_status
        - reason_codes
        - confidence
        - last_updated
      properties:
        vehicle_id:
          type: string
          format: uuid
        depot_id:
          type: string
          format: uuid
        service_date:
          type: string
          format: date
          description: Depot-local dispatch service date used for duty and block lookup.
        assessment_time:
          type: string
          format: date-time
          description: Operational time used for this readiness calculation. This matches the collection assessment_time when returned inside ReadinessAssessmentCollection.
        block_id:
          type: [string, "null"]
          format: uuid
        readiness_status:
          $ref: "#/components/schemas/ReadinessStatus"
        reason_codes:
          type: array
          items:
            $ref: "#/components/schemas/ReadinessReasonCode"
        current_soc_percent:
          type: [number, "null"]
        current_soc_source:
          oneOf:
            - $ref: "#/components/schemas/TelemetrySource"
            - type: "null"
        current_soc_observed_at:
          type: [string, "null"]
          format: date-time
        departure_time:
          type: [string, "null"]
          format: date-time
        required_departure_soc_percent:
          type: [number, "null"]
        predicted_soc_at_departure_percent:
          type: [number, "null"]
        predicted_arrival_soc_percent:
          type: [number, "null"]
        predicted_energy_required_wh:
          type: [number, "null"]
        readiness_priority:
          type: [integer, "null"]
          description: Lower number means higher charge/readiness urgency.
        yard_context:
          oneOf:
            - $ref: "#/components/schemas/YardContext"
            - type: "null"
          description: Latest yard-space context used when physical accessibility or depot position affects readiness.
        confidence:
          $ref: "#/components/schemas/Confidence"
        last_updated:
          type: string
          format: date-time

    TelemetryObservationBase:
      type: object
      required: [source, observed_at, is_stale]
      properties:
        source:
          $ref: "#/components/schemas/TelemetrySource"
        source_system:
          type: [string, "null"]
          description: Upstream system or feed name when supplied.
        observed_at:
          type: string
          format: date-time
          description: Source timestamp for this observation.
        is_stale:
          type: boolean
          description: True when this field's latest source observation is older than the agreed freshness threshold.
        stale_reason:
          type: [string, "null"]

    StateOfChargeObservation:
      allOf:
        - $ref: "#/components/schemas/TelemetryObservationBase"
        - type: object
          required: [value, unit]
          properties:
            value:
              type: [number, "null"]
            unit:
              type: string
              enum: [percent]

    LocationTelemetryObservation:
      allOf:
        - $ref: "#/components/schemas/TelemetryObservationBase"
        - type: object
          required: [value, unit]
          properties:
            value:
              oneOf:
                - $ref: "#/components/schemas/Location"
                - type: "null"
            unit:
              type: string
              enum: [wgs84]

    SpeedTelemetryObservation:
      allOf:
        - $ref: "#/components/schemas/TelemetryObservationBase"
        - type: object
          required: [value, unit]
          properties:
            value:
              type: [number, "null"]
            unit:
              type: string
              enum: [kph]

    OdometerTelemetryObservation:
      allOf:
        - $ref: "#/components/schemas/TelemetryObservationBase"
        - type: object
          required: [value, unit]
          properties:
            value:
              type: [number, "null"]
            unit:
              type: string
              enum: [km]

    VehicleChargingContext:
      type: object
      required: [charger_id, connector_id, active_transaction_id]
      properties:
        charger_id:
          type: [string, "null"]
          format: uuid
        connector_id:
          type: [string, "null"]
          format: uuid
        active_transaction_id:
          type: [string, "null"]
          format: uuid

    VehicleTelemetrySnapshot:
      type: object
      description: |
        Latest known telemetry snapshot for one vehicle. Each telemetry field is an observation with its own value, unit, source, observed timestamp, and stale-data state.
      required:
        - vehicle_id
        - depot_id
        - vehicle_state
        - is_stale
        - last_updated
      properties:
        vehicle_id:
          type: string
          format: uuid
        depot_id:
          type: string
          format: uuid
        vehicle_state:
          $ref: "#/components/schemas/VehicleState"
        soc:
          oneOf:
            - $ref: "#/components/schemas/StateOfChargeObservation"
            - type: "null"
        location:
          oneOf:
            - $ref: "#/components/schemas/LocationTelemetryObservation"
            - type: "null"
        speed:
          oneOf:
            - $ref: "#/components/schemas/SpeedTelemetryObservation"
            - type: "null"
        odometer:
          oneOf:
            - $ref: "#/components/schemas/OdometerTelemetryObservation"
            - type: "null"
        charging_context:
          oneOf:
            - $ref: "#/components/schemas/VehicleChargingContext"
            - type: "null"
        is_stale:
          type: boolean
        stale_reason:
          type: [string, "null"]
        last_updated:
          type: string
          format: date-time

    VehicleTelemetrySnapshotCollection:
      type: object
      required: [depot_id, generated_at, refresh_after_seconds, vehicles]
      properties:
        depot_id:
          type: string
          format: uuid
        generated_at:
          type: string
          format: date-time
        refresh_after_seconds:
          type: integer
          example: 60
        vehicles:
          type: array
          items:
            $ref: "#/components/schemas/VehicleTelemetrySnapshot"

    RangePredictionRequest:
      type: object
      description: |
        Optional override object for range prediction.

        Standard request: BetterFleet only requires the vehicle_id path parameter. The service uses that vehicle identifier to derive the remaining input data from internal sources, including the vehicle's latest known SoC, depot/location context, active or next block where applicable, vehicle model, battery capacity and health, vehicle mass, passenger/load assumptions, HVAC and drivetrain parameters, weather for the prediction window, reserve SoC, and minimum safe SoC.

        Current internal implementation note: the Digital Twin range estimation service accepts data equivalent to a RangeRequestOverride object: vehicle_id, location, block, range_unit, passenger_count, override_start_time, override_end_time, override_expected_km_per_hour, override_aux_power_per_km, override_reserve_soc, override_current_soc, override_minimum_soc, and optional vehicle_details. Those fields are internal derivation inputs or optional scenario overrides, not mandatory Operations API client inputs.
      properties:
        vehicle_id:
          type: string
          format: uuid
          description: Optional body echo. If supplied, it must match the vehicle_id path parameter. The path parameter is authoritative.
        depot_id:
          type: [string, "null"]
          format: uuid
          description: Optional override. If omitted, BetterFleet derives depot context from the vehicle's latest known operational state.
        location:
          oneOf:
            - $ref: "#/components/schemas/Location"
            - type: "null"
          description: Optional location override corresponding to the internal RangeRequestOverride location. If omitted, BetterFleet derives the vehicle's latest depot, yard, or telemetry location.
        prediction_time:
          type: [string, "null"]
          format: date-time
          description: Optional override. If omitted, BetterFleet predicts from the current evaluation time.
        service_date:
          type: [string, "null"]
          format: date
          description: Optional override for dispatch-date context.
        range_unit:
          oneOf:
            - $ref: "#/components/schemas/RangeUnit"
            - type: "null"
          description: Optional range unit override corresponding to the internal range_unit. If omitted, BetterFleet uses km for canonical range outputs.
        current_soc_percent:
          type: [number, "null"]
          description: Optional override. If omitted, BetterFleet uses latest known SoC.
        current_soc_source:
          oneOf:
            - $ref: "#/components/schemas/TelemetrySource"
            - type: "null"
        block_id:
          type: [string, "null"]
          format: uuid
          description: Optional dispatch block override. When supplied, block_feasibility is returned.
        route_distance_km:
          type: [number, "null"]
          description: Optional route distance override. If omitted, BetterFleet derives distance or energy need from block and route data where available.
        prediction_window_start:
          type: [string, "null"]
          format: date-time
          description: Optional start time override corresponding to the internal override_start_time.
        prediction_window_end:
          type: [string, "null"]
          format: date-time
          description: Optional end time override corresponding to the internal override_end_time.
        expected_km_per_hour:
          type: [number, "null"]
          description: Optional average distance-rate assumption corresponding to the internal override_expected_km_per_hour.
        auxiliary_energy_wh_per_km:
          type: [number, "null"]
          description: Optional auxiliary energy-per-distance override in Wh/km, corresponding to the internal override_aux_power_per_km value. If omitted, BetterFleet derives auxiliary load from vehicle, HVAC, weather, and operating assumptions.
        passenger_count:
          type: [integer, "null"]
          minimum: 0
          description: Optional load override. If omitted, BetterFleet derives passenger/load assumptions from schedule, vehicle defaults, or agreed operating assumptions.
        reserve_soc_percent:
          type: [number, "null"]
          description: Optional reserve SoC override expressed as 0-100 percent. The internal implementation uses a fractional reserve value.
        minimum_safe_soc_percent:
          type: [number, "null"]
          description: Optional minimum safe SoC override expressed as 0-100 percent. The internal implementation uses a fractional minimum value.
        vehicle_model_override:
          oneOf:
            - $ref: "#/components/schemas/VehicleModelOverride"
            - type: "null"
          description: Optional what-if vehicle model override. Standard predictions derive this from BetterFleet's internal vehicle model.
        weather_profile:
          type: [string, "null"]
          description: Optional weather profile identifier or agreed free-text assumption.
        load_assumption:
          type: [string, "null"]
          description: Optional passenger/load assumption identifier or agreed free-text assumption.

    VehicleModelOverride:
      type: object
      description: |
        Optional vehicle model override for scenario testing. Standard Operations API clients should not need to send this object because BetterFleet derives these values from internal vehicle model data using vehicle_id.
      properties:
        passenger_count_maximum:
          type: [integer, "null"]
        passenger_count_nominal:
          type: [integer, "null"]
        tare_mass_kg:
          type: [number, "null"]
        gross_vehicle_mass_kg:
          type: [number, "null"]
        auxiliary_power_nominal_kw:
          type: [number, "null"]
        heater_type:
          type: [string, "null"]
        heater_power_maximum_kw:
          type: [number, "null"]
        chiller_type:
          type: [string, "null"]
        chiller_power_maximum_kw:
          type: [number, "null"]
        relative_humidity_setpoint:
          type: [number, "null"]
        fresh_air_ratio:
          type: [number, "null"]
        air_circulation_power_nominal_kw:
          type: [number, "null"]
        motor_power_maximum_kw:
          type: [number, "null"]
        efficiency_motor:
          type: [number, "null"]
        efficiency_generator:
          type: [number, "null"]
        energy_recovery_factor:
          type: [number, "null"]
        regeneration_cutoff_speed_kph:
          type: [number, "null"]
        efficiency_final_drive:
          type: [number, "null"]
        efficiency_wheel:
          type: [number, "null"]
        battery_capacity_rated_kwh:
          type: [number, "null"]
        battery_state_of_health:
          type: [number, "null"]
        battery_efficiency_one_way:
          type: [number, "null"]

    RangePredictionResponse:
      type: object
      required:
        - vehicle_id
        - depot_id
        - prediction_time
        - confidence
        - basis
        - warnings
        - last_updated
      properties:
        vehicle_id:
          type: string
          format: uuid
        depot_id:
          type: string
          format: uuid
        prediction_time:
          type: string
          format: date-time
        current_soc_percent:
          type: [number, "null"]
        usable_energy_wh:
          type: [number, "null"]
        reserve_soc_percent:
          type: [number, "null"]
        predicted_range_km:
          type: [number, "null"]
          description: Canonical predicted range in kilometres. This remains the stable metric output even when a scenario request includes range_unit.
        confidence:
          $ref: "#/components/schemas/Confidence"
        basis:
          type: string
          enum: [digital_twin, dispatch_energy_estimate, fallback_charging_model, simple_model]
        block_feasibility:
          oneOf:
            - $ref: "#/components/schemas/BlockFeasibility"
            - type: "null"
        warnings:
          type: array
          items:
            type: string
        last_updated:
          type: string
          format: date-time

    BlockFeasibility:
      allOf:
        - $ref: "#/components/schemas/VehicleReadinessAssessment"
        - type: object
          required: [is_feasible]
          properties:
            is_feasible:
              type: boolean

    ChargerConnectorStatus:
      type: object
      required:
        - charger_id
        - charger_name
        - depot_id
        - charger_connection_state
        - charger_status
        - in_service
        - last_updated
      properties:
        charger_id:
          type: string
          format: uuid
        charger_name:
          type: string
        depot_id:
          type: string
          format: uuid
        charger_connection_state:
          type: string
          enum: [ONLINE, OFFLINE]
        charger_status:
          $ref: "#/components/schemas/BetterFleetChargerStatus"
        in_service:
          type: boolean
        connector_id:
          type: [string, "null"]
          format: uuid
        connector_number:
          type: [integer, "null"]
        connector_status:
          oneOf:
            - $ref: "#/components/schemas/BetterFleetConnectorStatus"
            - type: "null"
          description: BetterFleet-normalized connector operational status for dispatch and dashboard use.
        ocpp_connector_status:
          oneOf:
            - $ref: "#/components/schemas/OcppConnectorStatus"
            - type: "null"
          description: Latest raw OCPP connector status when supplied by the charging infrastructure.
        active_transaction_id:
          type: [string, "null"]
          format: uuid
        connected_vehicle_id:
          type: [string, "null"]
          format: uuid
        allocated_power_kw:
          type: [number, "null"]
        fault_code:
          type: [string, "null"]
        last_updated:
          type: string
          format: date-time

    ChargerStatusSnapshot:
      type: object
      required: [depot_id, generated_at, refresh_after_seconds, chargers]
      properties:
        depot_id:
          type: string
          format: uuid
        generated_at:
          type: string
          format: date-time
        refresh_after_seconds:
          type: integer
          example: 60
        chargers:
          type: array
          items:
            $ref: "#/components/schemas/ChargerConnectorStatus"

    ActiveSessionPairing:
      type: object
      required:
        - session_id
        - transaction_number
        - charger_id
        - connector_id
        - connector_number
        - depot_id
        - session_state
        - ocpp_connector_status
        - start_timestamp
        - last_updated
      properties:
        session_id:
          type: string
          format: uuid
        transaction_number:
          type: integer
        vehicle_id:
          type: [string, "null"]
          format: uuid
        vehicle_id_tag:
          type: [string, "null"]
        charger_id:
          type: string
          format: uuid
        connector_id:
          type: string
          format: uuid
        connector_number:
          type: integer
        depot_id:
          type: string
          format: uuid
        session_state:
          $ref: "#/components/schemas/NormalizedSessionState"
        ocpp_connector_status:
          $ref: "#/components/schemas/OcppConnectorStatus"
        ocpp_stop_reason:
          type: [string, "null"]
          description: OCPP StopTransaction reason or charger-specific stop reason, when available.
        start_timestamp:
          type: string
          format: date-time
        stop_timestamp:
          type: [string, "null"]
          format: date-time
        current_power_kw:
          type: [number, "null"]
        soc_percent:
          type: [number, "null"]
        energy_delivered_wh:
          type: [number, "null"]
        last_updated:
          type: string
          format: date-time

    ActiveSessionsSnapshot:
      type: object
      required: [depot_id, generated_at, refresh_after_seconds, sessions]
      properties:
        depot_id:
          type: string
          format: uuid
        generated_at:
          type: string
          format: date-time
        refresh_after_seconds:
          type: integer
          example: 30
        sessions:
          type: array
          items:
            $ref: "#/components/schemas/ActiveSessionPairing"

    ChargingPriorityQueue:
      type: object
      required: [depot_id, dispatch_date, generated_at, refresh_after_seconds, queue]
      properties:
        depot_id:
          type: string
          format: uuid
        dispatch_date:
          type: string
          format: date
        generated_at:
          type: string
          format: date-time
        refresh_after_seconds:
          type: integer
        queue:
          type: array
          items:
            $ref: "#/components/schemas/ChargingPriorityQueueItem"

    ChargingPriorityQueueItem:
      type: object
      required:
        - rank
        - charging_request_id
        - vehicle_id
        - required_by
        - min_target_soc
        - max_target_soc
        - source_block_ids
        - confidence
        - warnings
      properties:
        rank:
          type: integer
          minimum: 1
        charging_request_id:
          type: string
        vehicle_id:
          type: string
          format: uuid
        assigned_charger_id:
          type: [string, "null"]
          format: uuid
        required_by:
          type: string
          format: date-time
        expected_arrival_time_at_charging_point:
          type: [string, "null"]
          format: date-time
        expected_soc_at_arrival:
          type: [number, "null"]
        min_target_soc:
          type: number
        max_target_soc:
          type: number
        source_block_ids:
          type: array
          items:
            type: string
            format: uuid
        confidence:
          $ref: "#/components/schemas/Confidence"
        warnings:
          type: array
          items:
            type: string

    DispatchRecommendationSet:
      type: object
      required: [depot_id, service_date, generated_at, recommendations]
      properties:
        depot_id:
          type: string
          format: uuid
        service_date:
          type: string
          format: date
        generated_at:
          type: string
          format: date-time
        recommendations:
          type: array
          items:
            $ref: "#/components/schemas/DispatchRecommendation"

    DispatchRecommendation:
      type: object
      required: [block_id, departure_time, recommendation, readiness_status, score, reason_codes]
      properties:
        block_id:
          type: string
          format: uuid
        departure_time:
          type: string
          format: date-time
        recommended_vehicle_id:
          type: [string, "null"]
          format: uuid
        recommendation:
          type: string
          enum: [assign, keep_assignment, review, do_not_assign]
        readiness_status:
          $ref: "#/components/schemas/ReadinessStatus"
        score:
          type: number
          minimum: 0
          maximum: 1
        reason_codes:
          type: array
          items:
            $ref: "#/components/schemas/ReadinessReasonCode"
        alternatives:
          type: array
          items:
            $ref: "#/components/schemas/DispatchRecommendationAlternative"

    DispatchRecommendationAlternative:
      type: object
      required: [vehicle_id, readiness_status]
      properties:
        vehicle_id:
          type: string
          format: uuid
        readiness_status:
          $ref: "#/components/schemas/ReadinessStatus"

    WebhookEventType:
      type: string
      enum:
        - charging_session.started
        - charging_session.active
        - charging_session.paused
        - charging_session.faulted
        - charging_session.stopped
        - charger.status_changed
        - vehicle.readiness_changed
        - vehicle.telemetry_changed
        - vehicle.telemetry_stale
        - yard.space_state_changed
        - yard.occupant_changed

    WebhookSubscriptionRequest:
      type: object
      required: [target_url, event_types]
      properties:
        target_url:
          type: string
          format: uri
          description: Integration HTTPS endpoint that will receive event POST requests.
        event_types:
          type: array
          items:
            $ref: "#/components/schemas/WebhookEventType"
        secret_rotation_hint:
          type: [string, "null"]
          description: Optional integration-provided label for webhook secret rotation tracking.

    WebhookSubscription:
      allOf:
        - $ref: "#/components/schemas/WebhookSubscriptionRequest"
        - type: object
          required: [subscription_id, created_at, active]
          properties:
            subscription_id:
              type: string
              format: uuid
            created_at:
              type: string
              format: date-time
            active:
              type: boolean

    EventEnvelopeBase:
      type: object
      required: [event_id, event_type, event_version, occurred_at, published_at, depot_id]
      properties:
        event_id:
          type: string
          format: uuid
          description: Idempotency key for event consumers.
        event_type:
          $ref: "#/components/schemas/WebhookEventType"
        event_version:
          type: string
          example: "1.0"
        occurred_at:
          type: string
          format: date-time
        published_at:
          type: string
          format: date-time
        depot_id:
          type: string
          format: uuid

    VehicleReadinessChangedEvent:
      allOf:
        - $ref: "#/components/schemas/EventEnvelopeBase"
        - type: object
          required: [data]
          properties:
            event_type:
              type: string
              enum: [vehicle.readiness_changed]
            data:
              $ref: "#/components/schemas/VehicleReadinessChangedData"

    VehicleReadinessChangedData:
      type: object
      description: |
        Readiness-change event payload. service_date identifies the depot-local dispatch service date used for block lookup. assessment_time identifies the operational instant the readiness result applies to. last_updated is the BetterFleet readiness read-model timestamp for ordering state changes; event envelope published_at is only the webhook delivery time.
      required:
        - vehicle_id
        - service_date
        - assessment_time
        - previous_readiness_status
        - readiness_status
        - previous_reason_codes
        - reason_codes
        - confidence
        - last_updated
      properties:
        vehicle_id:
          type: string
          format: uuid
        block_id:
          type: [string, "null"]
          format: uuid
        service_date:
          type: string
          format: date
          description: Depot-local dispatch service date used for duty and block lookup.
        assessment_time:
          type: string
          format: date-time
          description: Operational time used for the readiness calculation. Current-readiness events use the request-time or evaluation-time "now"; predictive events use the future assessment time.
        previous_readiness_status:
          $ref: "#/components/schemas/ReadinessStatus"
        readiness_status:
          $ref: "#/components/schemas/ReadinessStatus"
        previous_reason_codes:
          type: array
          items:
            $ref: "#/components/schemas/ReadinessReasonCode"
        reason_codes:
          type: array
          items:
            $ref: "#/components/schemas/ReadinessReasonCode"
        current_soc_percent:
          type: [number, "null"]
        current_soc_observed_at:
          type: [string, "null"]
          format: date-time
        required_departure_soc_percent:
          type: [number, "null"]
        departure_time:
          type: [string, "null"]
          format: date-time
        predicted_soc_at_departure_percent:
          type: [number, "null"]
        predicted_arrival_soc_percent:
          type: [number, "null"]
        readiness_priority:
          type: [integer, "null"]
          description: Lower number means higher charge/readiness urgency.
        confidence:
          $ref: "#/components/schemas/Confidence"
        last_updated:
          type: string
          format: date-time
          description: UTC time when BetterFleet last updated this readiness state. Consumers should use this value with vehicle_id, block_id, service_date, and assessment_time for state ordering and deduplication.

    VehicleTelemetryChangedEvent:
      allOf:
        - $ref: "#/components/schemas/EventEnvelopeBase"
        - type: object
          required: [data]
          properties:
            event_type:
              type: string
              enum: [vehicle.telemetry_changed, vehicle.telemetry_stale]
            data:
              $ref: "#/components/schemas/VehicleTelemetryChangedData"

    VehicleTelemetryChangedData:
      type: object
      required: [vehicle_id, telemetry_snapshot]
      properties:
        vehicle_id:
          type: string
          format: uuid
        previous_telemetry_snapshot:
          oneOf:
            - $ref: "#/components/schemas/VehicleTelemetrySnapshot"
            - type: "null"
        changed_fields:
          type: array
          items:
            type: string
            enum: [soc, location, speed, odometer, vehicle_state, charging_context, stale_state]
          description: Names of telemetry fields that changed when known.
        telemetry_snapshot:
          $ref: "#/components/schemas/VehicleTelemetrySnapshot"

    ChargingSessionEvent:
      allOf:
        - $ref: "#/components/schemas/EventEnvelopeBase"
        - type: object
          required: [data]
          properties:
            event_type:
              type: string
              enum:
                - charging_session.started
                - charging_session.active
                - charging_session.paused
                - charging_session.faulted
                - charging_session.stopped
            data:
              $ref: "#/components/schemas/ChargingSessionEventData"

    ChargingSessionEventData:
      type: object
      required:
        - session_id
        - transaction_number
        - charger_id
        - connector_id
        - connector_number
        - session_state
        - ocpp_connector_status
      properties:
        session_id:
          type: string
          format: uuid
        transaction_number:
          type: integer
        vehicle_id:
          type: [string, "null"]
          format: uuid
        charger_id:
          type: string
          format: uuid
        connector_id:
          type: string
          format: uuid
        connector_number:
          type: integer
        session_state:
          $ref: "#/components/schemas/NormalizedSessionState"
        previous_session_state:
          oneOf:
            - $ref: "#/components/schemas/NormalizedSessionState"
            - type: "null"
        ocpp_connector_status:
          $ref: "#/components/schemas/OcppConnectorStatus"
        previous_ocpp_connector_status:
          oneOf:
            - $ref: "#/components/schemas/OcppConnectorStatus"
            - type: "null"
        ocpp_stop_reason:
          type: [string, "null"]
        soc_percent:
          type: [number, "null"]
        power_kw:
          type: [number, "null"]

    ChargerStatusChangedEvent:
      allOf:
        - $ref: "#/components/schemas/EventEnvelopeBase"
        - type: object
          required: [data]
          properties:
            event_type:
              type: string
              enum: [charger.status_changed]
            data:
              $ref: "#/components/schemas/ChargerStatusChangedData"

    ChargerStatusChangedData:
      type: object
      required: [charger_id, charger_connection_state, charger_status]
      properties:
        charger_id:
          type: string
          format: uuid
        connector_id:
          type: [string, "null"]
          format: uuid
        previous_charger_connection_state:
          type: [string, "null"]
          enum: [ONLINE, OFFLINE, null]
        charger_connection_state:
          type: string
          enum: [ONLINE, OFFLINE]
        previous_charger_status:
          oneOf:
            - $ref: "#/components/schemas/BetterFleetChargerStatus"
            - type: "null"
        charger_status:
          $ref: "#/components/schemas/BetterFleetChargerStatus"
        previous_connector_status:
          oneOf:
            - $ref: "#/components/schemas/BetterFleetConnectorStatus"
            - type: "null"
        connector_status:
          oneOf:
            - $ref: "#/components/schemas/BetterFleetConnectorStatus"
            - type: "null"
        previous_ocpp_connector_status:
          oneOf:
            - $ref: "#/components/schemas/OcppConnectorStatus"
            - type: "null"
          description: Previous raw OCPP connector status when supplied.
        ocpp_connector_status:
          oneOf:
            - $ref: "#/components/schemas/OcppConnectorStatus"
            - type: "null"
          description: Current raw OCPP connector status when supplied.
        fault_code:
          type: [string, "null"]

    YardSpaceStateChangedEvent:
      allOf:
        - $ref: "#/components/schemas/EventEnvelopeBase"
        - type: object
          required: [data]
          properties:
            event_type:
              type: string
              enum: [yard.space_state_changed]
            data:
              $ref: "#/components/schemas/YardSpaceStateChangedData"

    YardSpaceStateChangedData:
      type: object
      required: [space_id, previous_space_state, space_state, observed_at]
      properties:
        space_id:
          type: string
        previous_space_state:
          oneOf:
            - $ref: "#/components/schemas/YardSpaceState"
            - type: "null"
        space_state:
          $ref: "#/components/schemas/YardSpaceState"
        previous_occupant_vehicle_id:
          type: [string, "null"]
          format: uuid
        occupant_vehicle_id:
          type: [string, "null"]
          format: uuid
        observed_at:
          type: string
          format: date-time
        source_system:
          type: [string, "null"]
        yard_context:
          oneOf:
            - $ref: "#/components/schemas/YardContext"
            - type: "null"

    YardOccupantChangedEvent:
      allOf:
        - $ref: "#/components/schemas/EventEnvelopeBase"
        - type: object
          required: [data]
          properties:
            event_type:
              type: string
              enum: [yard.occupant_changed]
            data:
              $ref: "#/components/schemas/YardOccupantChangedData"

    YardOccupantChangedData:
      type: object
      required: [space_id, occupant, observed_at]
      properties:
        space_id:
          type: string
        previous_occupant:
          oneOf:
            - $ref: "#/components/schemas/YardOccupant"
            - type: "null"
        occupant:
          oneOf:
            - $ref: "#/components/schemas/YardOccupant"
            - type: "null"
        space_state:
          $ref: "#/components/schemas/YardSpaceState"
        observed_at:
          type: string
          format: date-time
        source_system:
          type: [string, "null"]

    RateLimitPolicy:
      type: object
      required: [generated_at, policy_status, recommendations]
      properties:
        generated_at:
          type: string
          format: date-time
        policy_status:
          type: string
          enum: [indicative, agreed, enforced]
          description: Indicates whether the policy is only a starting point or an agreed production setting.
        default_limit_per_minute:
          type: [integer, "null"]
          description: To be confirmed during production sizing.
        burst_limit:
          type: [integer, "null"]
        fleet_size_assumption:
          type: [integer, "null"]
          example: 500
        recommendations:
          type: array
          items:
            $ref: "#/components/schemas/PollingRecommendation"

    PollingRecommendation:
      type: object
      required: [data_class, recommended_pattern]
      properties:
        data_class:
          type: string
          enum:
            - charger_session_status
            - vehicle_telemetry
            - predictive_readiness
            - yard_state
            - historical_reporting
        recommended_pattern:
          type: string
          enum: [webhook_preferred, polling_acceptable, batch_preferred]
        polling_interval_seconds:
          type: [integer, "null"]
        notes:
          type: string

    IntegrationEnvironments:
      type: object
      required: [documentation_url, environments]
      properties:
        documentation_url:
          type: string
          format: uri
          description: Public API OpenAPI or documentation URL supplied during integration onboarding.
        environments:
          type: array
          items:
            $ref: "#/components/schemas/IntegrationEnvironment"

    IntegrationEnvironment:
      type: object
      required: [name, purpose, base_url, availability]
      properties:
        name:
          type: string
          enum: [development, staging, production]
        purpose:
          type: string
        base_url:
          type: string
          format: uri
        availability:
          type: string
          description: Access availability and approval status for integration partners.
        dedicated_test_tenant_available:
          type: boolean

    PublicApiVersion:
      type: object
      required: [version, description]
      properties:
        version:
          type: string
          example: pre
        description:
          type: string

x-betterfleet-adr-review:
  relevant_decisions:
    - "0013 - RESTful TITLE GraphQL: use REST as the default public API style."
    - "0020 - API backwards compatibility and versioning: prefer additive changes and deliberate versioning for breaking changes."
    - "0009 - Use Timezone Aware DateTimes and UTC: use timezone-aware request timestamps and UTC response timestamps."
    - "0023 - Domain Event-Driven Architecture: use event push for domain-significant operational changes where appropriate."
    - "0029 - Adopt RFC 9457 for HTTP Error Responses: future public errors should move toward problem details; current public API still exposes string-detail errors in places."
    - "0031 - Adopt DDD, Hexagonal Architecture, and CQRS for Python Domain Services: expose read-model DTOs rather than internal aggregates."
  notes:
    - This is a pre-release integration contract draft, not an implemented endpoint set.
    - mTLS and final rate limits require security and production-sizing confirmation.
