Skip to main content

Overview

The Trailers API provides endpoints for querying trailer locations and receiving real-time updates. Trailers represent cargo units that are tracked independently from tractors via GPS devices. Database Table: trailers2 (aliased as trailers in code) Data Sources:
  • Terminal API (/tsp/v1/trailers/locations) - Real-time trailer GPS locations
  • Terminal API (/tsp/v1/trailers) - Static trailer info (VIN, make, model, etc.)

Get Current Trailer Locations

GET /api/trailers/current

Get current trailer locations from the database. This is a DB-first read endpoint with staleness metadata.
limit
integer
default:"None"
Maximum number of trailers to return (1-1000)
since
string
default:"None"
ISO 8601 timestamp - only return trailers updated after this time
Response (200 OK):
{
  "dataSource": "db",
  "refreshInProgress": false,
  "staleMs": 15000,
  "maxLastUpdatedAt": "2025-11-17T19:30:00.000Z",
  "items": [
    {
      "terminal_trailer_id": "trl_01D8ZQFGHVJ858NBF2Q7DV9MNC",
      "trailer_id": "550e8400-e29b-41d4-a716-446655440000",
      "latitude": 37.7749295,
      "longitude": -122.4194155,
      "updated_at": "2025-11-17T19:30:00.000Z",
      "located_at": "2025-11-17T19:29:45.000Z",
      "address": "1.5 miles from Austin, TX",
      "heading": 275.5,
      "speed": 65.3,
      "provider": "geotab",
      "make": "Wabash",
      "model": "DuraPlate",
      "vin": "1JJV532D4ML123456",
      "name": "Trailer 53668"
    }
  ],
  "itemsCount": 1
}
Response (202 Accepted - No data yet):
{
  "dataSource": "db",
  "refreshInProgress": true,
  "staleMs": null,
  "maxLastUpdatedAt": null,
  "items": []
}
How it works:
  1. Database Query: Calls get_latest_trailer_locations() which queries the trailers2 table
  2. Window Function: Uses PostgreSQL ROW_NUMBER() to get latest row per terminal_trailer_id, ordered by updated_at DESC
  3. Filters Applied:
    • Only trailers with non-null terminal_trailer_id, latitude, and longitude
    • Optional since timestamp filter
  4. Cold-Start Handling: If no data and poll in progress, waits up to COLD_START_WAIT_MS (250ms) before returning 202
  5. Staleness Calculation: Compares current time to max(updated_at)
Response Headers:
HeaderValue
X-Data-SourceAlways db
X-Stale-MsMilliseconds since most recent update
ETagMD5 hash of payload
Last-ModifiedHTTP date of most recent update
Retry-AfterSeconds to wait (only on 202)

Real-Time Trailer Stream

GET /api/trailers/stream

Server-Sent Events (SSE) endpoint for real-time trailer location updates. Event Types:
Sent immediately when client connects.
event: connected
data: {"client_id": "uuid-string"}
retry: 5000
Sent when new trailer location data is available (every 60 seconds during active polling).
event: trailer_update
data: {
  "trailers": [...],
  "trailers_count": 50,
  "polled_at": "2025-11-17T19:30:00.000Z"
}
Sent every hour with static trailer information (make, model, VIN, etc.).
event: trailer_info
data: {
  "trailers": [...],
  "trailers_count": 50,
  "fetched_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, receives unique client_id
  2. Client Registration: SSEConnectionManager creates asyncio queue for this client
  3. Polling Wake-up: Notifies TrailerPollingService to switch from slow (300s) to fast (60s) polling
  4. Initial Data: Reads from trailers2 table via get_latest_trailer_locations() and sends first trailer_update
  5. Live Updates: TrailerPollingService broadcasts to all connected clients after each poll
  6. Static Info: Every hour, fetches static trailer info and sends as trailer_info event
  7. Heartbeat: Sends :heartbeat every 30 seconds
  8. Disconnect: Removes client from manager on disconnect
Frontend Example:
const eventSource = new EventSource('/api/trailers/stream');

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

eventSource.addEventListener('trailer_update', (event) => {
  const batch = JSON.parse(event.data);
  console.log(`Received ${batch.trailers_count} trailers`);
  
  batch.trailers.forEach(trailer => {
    updateMapMarker(trailer.terminal_trailer_id, {
      lat: trailer.latitude,
      lng: trailer.longitude
    });
  });
});

eventSource.addEventListener('trailer_info', (event) => {
  const batch = JSON.parse(event.data);
  // Update static info like make, model, VIN
  batch.trailers.forEach(trailer => {
    updateTrailerDetails(trailer.terminal_trailer_id, {
      make: trailer.make,
      model: trailer.model,
      vin: trailer.vin
    });
  });
});

Data Model

Trailers Table Schema

ColumnTypeDescription
idVARCHAR(8)Legacy 8-char ID
trailer_idUUIDFull UUID for internal tracking
company_idVARCHARAlways “TERM” for Terminal data
terminal_trailer_idVARCHARTerminal trailer ID (unique, trl_xxx)
last_location_latitudeNUMERICGPS latitude
last_location_longitudeNUMERICGPS longitude
last_located_atTIMESTAMPTZGPS timestamp from provider
last_location_addressVARCHARFormatted address from provider
last_location_headingNUMERICHeading in degrees (0-360)
last_location_speedNUMERICSpeed in mph
last_location_providerVARCHARELD provider (geotab, motive, etc.)
makeVARCHARTrailer manufacturer
modelVARCHARTrailer model
model_yearINTManufacturing year
vinVARCHARVehicle Identification Number
physical_trailer_idVARCHARSerial number/physical ID
license_stateVARCHARLicense plate state
license_noVARCHARLicense plate number
statuscodeVARCHARTerminal status (active/inactive)
is_activeBOOLEANDerived from statuscode
nameVARCHARTrailer name/identifier
updated_atTIMESTAMPTZLast DB update

Background Services

TrailerPollingService

Runs as a background service started during application lifespan. Location Polling:
  • Interval: 60 seconds (when SSE clients connected), 300 seconds (idle)
  • Source: Terminal API /tsp/v1/trailers/locations
  • Target: trailers2 table via bulk_upsert_trailer_locations()
  • Deduplication: Only keeps latest record per terminal_trailer_id
Static Info Polling:
  • Interval: Every hour (3600 seconds)
  • Source: Terminal API /tsp/v1/trailers
  • Target: trailers2 table via bulk_upsert_trailer_static()
  • Data: VIN, make, model, year, license plate, status
Dynamic Polling Strategy:
Client connects → notify_client_connected() → wake up poll loop
                                           → switch to fast polling (60s)
                                           
No clients     → slow polling (300s) for cache refresh only
Config:
  • LOCATION_POLL_ENABLED=true - Enable/disable polling
  • TERMINAL_POLLING_INTERVAL_SECONDS=60 - Fast poll interval
  • CACHE_TTL_SECONDS=300 - Slow poll interval (cache TTL)