Skip to main content

Service Status

GET /

Returns the current service status and polling configuration.
None
None
This endpoint accepts no parameters.
Response:
{
  "message": "Vehicle, Trailer & Driver Tracking API",
  "status": "running",
  "vehicle_polling_enabled": true,
  "trailer_polling_enabled": true,
  "driver_polling_enabled": true,
  "poll_interval_seconds": 60
}
How it works:
  • Returns static configuration values from core/settings.py
  • poll_interval_seconds is controlled by TERMINAL_POLLING_INTERVAL_SECONDS environment variable (default: 60)
  • All polling flags are controlled by LOCATION_POLL_ENABLED environment variable

Health Check

GET /health

Simple health check endpoint for load balancers and monitoring. Response:
{
  "status": "healthy"
}
How it works:
  • Returns immediately without database or external service checks
  • Used by Docker healthcheck and Kubernetes liveness probes

Vehicle Locations

GET /locations/current

Get current vehicle (tractor) locations from the database. This is a DB-first read endpoint that returns data with staleness metadata.
limit
integer
default:"None"
Maximum number of vehicles to return (1-1000)
since
string
default:"None"
ISO 8601 timestamp - only return vehicles updated after this time (e.g., 2025-11-11T16:00:00Z)
Response (200 OK):
{
  "dataSource": "db",
  "refreshInProgress": false,
  "staleMs": 15000,
  "maxLastUpdatedAt": "2025-11-17T19:30:00.000Z",
  "items": [
    {
      "vehicle_id": "vcl_01D8ZQFGHVJ858NBF2Q7DV9MNC",
      "latitude": 37.7749295,
      "longitude": -122.4194155,
      "updated_at": "2025-11-17T19:30:00.000Z",
      "located_at": "2025-11-17T19:29:45.000Z",
      "fuel_level": 75.5,
      "odometer_miles": 125432.5,
      "engine_hours": 3456.78,
      "mcleod_tractor_id": "735",
      "last_location_provider": "terminal",
      "fault_codes": []
    }
  ],
  "itemsCount": 1
}
Response (202 Accepted - No data yet):
{
  "dataSource": "db",
  "refreshInProgress": true,
  "staleMs": null,
  "maxLastUpdatedAt": null,
  "items": []
}
Response Headers:
HeaderDescription
X-Data-SourceAlways db
X-Stale-MsMilliseconds since most recent update
ETagMD5 hash of payload for caching
Last-ModifiedHTTP date of most recent update
Retry-AfterSeconds to wait (only on 202 status)
How it works:
  1. Database Query: Reads directly from the tractors table using get_latest_vehicle_locations() function
  2. Window Function: Uses PostgreSQL ROW_NUMBER() window function to get the latest row per vehicle_id, ordered by updated_at DESC
  3. Filters Applied:
    • Only vehicles with non-null vehicle_id, latitude, and longitude
    • Optional since timestamp filter
  4. Cold-Start Handling: If no data in DB and a poll is in progress, waits up to COLD_START_WAIT_MS (default: 250ms) before returning 202
  5. Staleness Calculation: Compares current time to max(updated_at) to calculate staleMs
  6. Stale Threshold: Data is considered stale if staleMs > VEHICLE_MAX_STALE_SECONDS * 1000 (default: 45 seconds)
Data Source:
  • The tractors table is populated by the LocationPollingService background service
  • Polling interval: 60 seconds when SSE clients connected, 300 seconds when idle
  • Data originates from Terminal API (/tsp/v1/vehicles/locations) which aggregates ELD providers (Motive, Samsara, Geotab)

Real-Time Location Stream

GET /stream/locations

Server-Sent Events (SSE) endpoint for real-time vehicle location updates. Event Types:
Sent immediately when client connects.
event: connected
data: {"client_id": "uuid-string"}
retry: 5000
Sent when new location data is available (every 60 seconds during active polling).
event: location_update
data: {
  "vehicles": [...],
  "vehicles_count": 25,
  "polled_at": "2025-11-17T19:30:00.000Z"
}
Sent every 30 seconds to keep connection alive.
:heartbeat
How it works:
  1. Connection: Client connects via EventSource API
  2. Client Registration: SSEConnectionManager creates a unique client ID and asyncio queue for this client
  3. Polling Wake-up: Notifies LocationPollingService to switch from slow polling (300s) to fast polling (60s)
  4. Initial Data: Immediately reads from tractors table via get_latest_vehicle_locations() and sends as first location_update event
  5. Live Updates: LocationPollingService broadcasts to all connected clients after each poll cycle
  6. Data Enrichment: Before broadcasting, locations are enriched with:
    • Driver names (via DriverNameMapper from Terminal API driver data)
    • Tractor numbers (via TractorNumberMapper)
  7. Heartbeat: Sends :heartbeat comment every SSE_HEARTBEAT_INTERVAL_SECONDS (default: 30s) to prevent connection timeout
  8. Disconnect Detection: Checks request.is_disconnected() every 1.5 seconds
  9. Cleanup: On disconnect, removes client from SSEConnectionManager
Data Flow Diagram:
Terminal API (/tsp/v1/vehicles/locations)

LocationPollingService (every 60s with clients, 300s without)

    ┌────┴────┐
    ↓         ↓
tractors   SSEConnectionManager
  table         ↓
    ↓      broadcast to all clients
    ↓           ↓
GET /locations/current    GET /stream/locations (SSE)
Frontend Example (JavaScript):
const eventSource = new EventSource('https://api.hemut.com/stream/locations');

eventSource.addEventListener('connected', (event) => {
  const data = JSON.parse(event.data);
  console.log('Connected with client ID:', data.client_id);
});

eventSource.addEventListener('location_update', (event) => {
  const batch = JSON.parse(event.data);
  console.log(`Received ${batch.vehicles_count} vehicles`);
  
  batch.vehicles.forEach(vehicle => {
    updateMapMarker(vehicle.vehicle_id, {
      lat: vehicle.latitude,
      lng: vehicle.longitude
    });
  });
});

eventSource.onerror = () => {
  console.log('Connection lost, auto-reconnecting...');
};
Response Headers:
HeaderValue
Content-Typetext/event-stream
Cache-Controlno-cache
Connectionkeep-alive
X-Accel-Bufferingno (disables nginx buffering)