Files
openjsp/API-DOCUMENTATION.md

20 KiB

GTFS API Documentation

Comprehensive API documentation for the Skopje Bus Tracker GTFS-based API.

Table of Contents


Overview

This API provides access to Skopje public transport data based on the GTFS (General Transit Feed Specification) standard. It combines static GTFS data with real-time information and historical analytics stored in TimescaleDB.

Key Features

  • GTFS-compliant static data (stops, routes)
  • Real-time vehicle positions and arrival predictions
  • Historical time-series data for analytics
  • Statistical analysis of delays and patterns

Upstream API

This application consumes data from the ModeShift GTFS API. For documentation on the upstream API endpoints:


OpenAPI Specification

A complete OpenAPI 3.0 specification is available at openapi.yaml. This machine-readable specification includes:

  • All API endpoints with parameters and responses
  • Request/response schemas and data models
  • Examples for all operations
  • Error response formats

Using the OpenAPI Spec

Interactive Documentation (Local):

Once the server is running, visit:

http://localhost:3000/api-docs.html

This provides an interactive Swagger UI where you can:

  • Browse all API endpoints
  • View request/response schemas
  • Try out API calls directly from the browser
  • Download the OpenAPI specification

Interactive Documentation with Swagger UI (Alternative):

# Using Docker
docker run -p 8080:8080 -e SWAGGER_JSON=/openapi.yaml -v $(pwd)/openapi.yaml:/openapi.yaml swaggerapi/swagger-ui

# Or use online editor
# Visit: https://editor.swagger.io/
# Then paste the contents of openapi.yaml

Generate Client Libraries:

# Install OpenAPI Generator
npm install -g @openapitools/openapi-generator-cli

# Generate TypeScript client
openapi-generator-cli generate -i openapi.yaml -g typescript-fetch -o ./client/typescript

# Generate Python client
openapi-generator-cli generate -i openapi.yaml -g python -o ./client/python

# Generate Go client
openapi-generator-cli generate -i openapi.yaml -g go -o ./client/go

VS Code Integration: Install the "OpenAPI (Swagger) Editor" extension to view and edit the spec with validation and IntelliSense.


Base URL

http://localhost:3000

For production, replace with your deployed server URL.


GTFS Static Data Endpoints

Get Configuration

Retrieve configuration including default stop and route information.

Endpoint: GET /api/config

Query Parameters:

Parameter Type Required Description
stopId string No Specific stop ID to query (defaults to configured default)
routeId string No Specific route ID to query (defaults to configured default)

Response:

{
  "stop": {
    "id": "1563",
    "code": "363",
    "name": "KARPOS II - STIV NAUMOV",
    "lat": 41.97964,
    "lon": 21.41283
  },
  "route": {
    "id": "125",
    "shortName": "7",
    "longName": "Centar - GTC - Karpos 4 - Karposh 2"
  },
  "defaults": {
    "stopId": "1563",
    "routeId": "125"
  }
}

Example:

curl "http://localhost:3000/api/config?stopId=1571&routeId=125"

Get All Stops

Retrieve all GTFS stops in the system.

Endpoint: GET /api/stops

Query Parameters: None

Response:

[
  {
    "id": "1563",
    "code": "363",
    "name": "KARPOS II - STIV NAUMOV",
    "lat": 41.97964,
    "lon": 21.41283
  },
  {
    "id": "1571",
    "code": "371",
    "name": "AMERICAN COLLEGE - VASIL GLAVINOV",
    "lat": 42.00437,
    "lon": 21.39687
  }
]

Example:

curl "http://localhost:3000/api/stops"

Use Cases:

  • Populate stop selection dropdowns
  • Build stop search functionality
  • Generate maps with all stops

Get All Routes

Retrieve all GTFS routes in the system.

Endpoint: GET /api/routes

Query Parameters: None

Response:

[
  {
    "id": "125",
    "shortName": "7",
    "longName": "Centar - GTC - Karpos 4 - Karposh 2"
  },
  {
    "id": "128",
    "shortName": "2",
    "longName": "Kisela Voda - Centar - 11 Oktomvri - Avtokomanda"
  }
]

Example:

curl "http://localhost:3000/api/routes"

Use Cases:

  • Display available routes
  • Filter by route number
  • Route selection interfaces

Real-Time Data Endpoints

Get Real-Time Arrivals

Get upcoming bus arrivals at a specific stop with real-time predictions.

Endpoint: GET /api/arrivals

Query Parameters:

Parameter Type Required Description
stopId string No Stop ID to query (defaults to configured default)
routeId string No Filter by specific route ID (defaults to configured default)

Response:

[
  {
    "arrivalTime": "2026-02-07T14:35:00.000Z",
    "scheduledTime": "2026-02-07T14:33:00.000Z",
    "minutesUntil": 8,
    "delaySeconds": 120,
    "headsign": "Karpos 2",
    "isRealtime": true,
    "realtimeState": "UPDATED"
  },
  {
    "arrivalTime": "2026-02-07T14:48:00.000Z",
    "scheduledTime": "2026-02-07T14:48:00.000Z",
    "minutesUntil": 21,
    "delaySeconds": 0,
    "headsign": "Centar",
    "isRealtime": true,
    "realtimeState": "SCHEDULED"
  }
]

Fields:

  • arrivalTime: Predicted arrival time (ISO 8601)
  • scheduledTime: Originally scheduled time (ISO 8601)
  • minutesUntil: Minutes until arrival (can be negative if bus passed)
  • delaySeconds: Delay in seconds (positive = late, negative = early)
  • headsign: Destination shown on bus
  • isRealtime: Whether prediction is based on real-time data
  • realtimeState: SCHEDULED, UPDATED, or CANCELED

Example:

curl "http://localhost:3000/api/arrivals?stopId=1563&routeId=125"

Notes:

  • Returns arrivals from 2 minutes ago up to configured minutes ahead
  • Results are sorted by arrival time
  • Data is also logged to database for historical analysis

Get Real-Time Vehicle Positions

Get current positions of all vehicles on a specific route.

Endpoint: GET /api/vehicles

Query Parameters:

Parameter Type Required Description
routeId string No Route ID to filter vehicles (defaults to configured default)

Response:

[
  {
    "id": 12345,
    "vehicleId": "MK-SK-1234",
    "label": "1234",
    "lat": 41.9981,
    "lon": 21.4254,
    "bearing": 87.5,
    "speed": 15.3,
    "timestamp": "2026-02-07T14:27:15Z",
    "tripId": "trip_789",
    "currentStopSequence": 0,
    "currentStatus": "IN_TRANSIT_TO"
  }
]

Fields:

  • id: Internal vehicle database ID
  • vehicleId: Vehicle identification number (license plate)
  • label: Bus inventory number (shown on bus)
  • lat/lon: Current GPS coordinates
  • bearing: Direction of travel (0-360 degrees, 0=North)
  • speed: Current speed in km/h
  • timestamp: Last position update time
  • tripId: GTFS trip ID vehicle is currently serving
  • currentStatus: IN_TRANSIT_TO, STOPPED_AT, or UNKNOWN

Example:

curl "http://localhost:3000/api/vehicles?routeId=125"

Notes:

  • Combines GTFS-RT trip updates with JSON vehicle API
  • Vehicle positions are logged to database
  • Updates typically every 10-30 seconds from source

Historical Data Endpoints

Get Database Statistics

Get overview statistics about stored historical data.

Endpoint: GET /api/stats/db

Query Parameters: None

Response:

{
  "vehiclePositions": {
    "totalRecords": 1523847,
    "oldestRecord": "2026-01-15T08:00:00.000Z",
    "newestRecord": "2026-02-07T14:27:30.000Z",
    "dataRetentionDays": 90
  },
  "arrivalRecords": {
    "totalRecords": 456231,
    "oldestRecord": "2026-01-15T08:00:00.000Z",
    "newestRecord": "2026-02-07T14:27:30.000Z",
    "dataRetentionDays": 90
  },
  "databaseSize": "2.3 GB",
  "status": "healthy"
}

Example:

curl "http://localhost:3000/api/stats/db"

Get Vehicle Position History

Retrieve historical GPS positions for a specific vehicle.

Endpoint: GET /api/history/vehicle/:vehicleId

Path Parameters:

Parameter Type Required Description
vehicleId string Yes Vehicle identification number or label

Query Parameters:

Parameter Type Required Default Description
hours integer No 24 Number of hours to look back

Response:

[
  {
    "time": "2026-02-07T10:00:00.000Z",
    "vehicleId": "MK-SK-1234",
    "routeId": "125",
    "tripId": "trip_789",
    "latitude": 41.9981,
    "longitude": 21.4254,
    "speed": 15.3,
    "bearing": 87.5,
    "currentStatus": "IN_TRANSIT_TO"
  }
]

Example:

curl "http://localhost:3000/api/history/vehicle/1234?hours=12"

Use Cases:

  • Visualize vehicle path on map
  • Analyze driving patterns
  • Calculate actual travel times

Get Route Vehicle History

Get historical positions of all vehicles that served a specific route.

Endpoint: GET /api/history/route/:routeId/vehicles

Path Parameters:

Parameter Type Required Description
routeId string Yes GTFS route ID

Query Parameters:

Parameter Type Required Default Description
hours integer No 24 Number of hours to look back

Response:

[
  {
    "time": "2026-02-07T10:00:00.000Z",
    "vehicleId": "1234",
    "routeId": "125",
    "tripId": "trip_789",
    "latitude": 41.9981,
    "longitude": 21.4254,
    "speed": 15.3,
    "bearing": 87.5
  }
]

Example:

curl "http://localhost:3000/api/history/route/125/vehicles?hours=6"

Use Cases:

  • Service frequency analysis
  • Route coverage visualization
  • Fleet utilization tracking

Get Stop Arrival History

Retrieve historical arrival records for a specific stop.

Endpoint: GET /api/history/stop/:stopId/arrivals

Path Parameters:

Parameter Type Required Description
stopId string Yes GTFS stop ID

Query Parameters:

Parameter Type Required Default Description
routeId string No (all) Filter by specific route
hours integer No 24 Number of hours to look back

Response:

[
  {
    "time": "2026-02-07T10:15:00.000Z",
    "stopId": "1563",
    "routeId": "125",
    "scheduledTime": "2026-02-07T10:15:00.000Z",
    "predictedTime": "2026-02-07T10:17:30.000Z",
    "delaySeconds": 150,
    "isRealtime": true,
    "headsign": "Karpos 2"
  }
]

Example:

curl "http://localhost:3000/api/history/stop/1563/arrivals?routeId=125&hours=48"

Statistics & Analytics Endpoints

Get Route Delay Statistics

Get delay statistics and performance metrics for a route.

Endpoint: GET /api/stats/route/:routeId/delays

Path Parameters:

Parameter Type Required Description
routeId string Yes GTFS route ID

Query Parameters:

Parameter Type Required Default Description
hours integer No 24 Time window for analysis

Response:

{
  "routeId": "125",
  "periodHours": 24,
  "statistics": {
    "totalArrivals": 287,
    "avgDelaySeconds": 62,
    "medianDelaySeconds": 45,
    "minDelaySeconds": -30,
    "maxDelaySeconds": 420,
    "stdDeviation": 78.5,
    "onTimePercentage": 68.3,
    "earlyPercentage": 8.7,
    "latePercentage": 23.0,
    "veryLatePercentage": 4.2
  },
  "thresholds": {
    "onTime": "±5 minutes",
    "veryLate": ">10 minutes"
  }
}

Example:

curl "http://localhost:3000/api/stats/route/125/delays?hours=72"

Definitions:

  • On-time: Within ±5 minutes of schedule
  • Early: More than 5 minutes early
  • Late: 5-10 minutes late
  • Very Late: More than 10 minutes late

Get Stop Delay Statistics

Get delay statistics for a specific stop.

Endpoint: GET /api/stats/stop/:stopId/delays

Path Parameters:

Parameter Type Required Description
stopId string Yes GTFS stop ID

Query Parameters:

Parameter Type Required Default Description
hours integer No 24 Time window for analysis

Response:

{
  "stopId": "1563",
  "periodHours": 24,
  "statistics": {
    "totalArrivals": 145,
    "avgDelaySeconds": 72,
    "medianDelaySeconds": 60,
    "onTimePercentage": 62.1,
    "routeBreakdown": [
      {
        "routeId": "125",
        "routeName": "7",
        "arrivals": 89,
        "avgDelaySeconds": 65
      },
      {
        "routeId": "128",
        "routeName": "2",
        "arrivals": 56,
        "avgDelaySeconds": 82
      }
    ]
  }
}

Example:

curl "http://localhost:3000/api/stats/stop/1563/delays?hours=48"

Use Cases:

  • Compare reliability across stops
  • Identify problematic locations
  • Plan transfer reliability

Get Route Hourly Pattern

Analyze delay patterns by hour of day for a route.

Endpoint: GET /api/stats/route/:routeId/hourly

Path Parameters:

Parameter Type Required Description
routeId string Yes GTFS route ID

Query Parameters:

Parameter Type Required Default Description
days integer No 7 Number of days to analyze

Response:

{
  "routeId": "125",
  "periodDays": 7,
  "hourlyPattern": [
    {
      "hour": 6,
      "arrivals": 42,
      "avgDelaySeconds": 15,
      "onTimePercentage": 85.7
    },
    {
      "hour": 7,
      "arrivals": 89,
      "avgDelaySeconds": 45,
      "onTimePercentage": 71.9
    },
    {
      "hour": 8,
      "arrivals": 124,
      "avgDelaySeconds": 125,
      "onTimePercentage": 52.4
    }
  ]
}

Example:

curl "http://localhost:3000/api/stats/route/125/hourly?days=14"

Use Cases:

  • Identify rush hour patterns
  • Compare morning vs evening reliability
  • Optimize schedules based on actual performance

Data Models

GTFS Stop

interface GtfsStop {
  stop_id: string;        // Unique stop identifier
  stop_code: string;      // Public-facing stop code
  stop_name: string;      // Stop name
  stop_lat: number;       // Latitude (WGS84)
  stop_lon: number;       // Longitude (WGS84)
}

GTFS Route

interface GtfsRoute {
  route_id: string;           // Unique route identifier
  route_short_name: string;   // Public route number (e.g., "7")
  route_long_name: string;    // Full route name
  route_type?: string;        // GTFS route type (0=Tram, 3=Bus, etc.)
}

Vehicle Position

interface VehiclePosition {
  time: Date;                // Timestamp (ISO 8601)
  vehicle_id: string;        // Vehicle identifier
  route_id: string;          // Route being served
  trip_id?: string;          // Current trip ID
  latitude: number;          // GPS latitude
  longitude: number;         // GPS longitude
  speed?: number;            // Speed in km/h
  bearing?: number;          // Direction (0-360°)
  current_status: string;    // IN_TRANSIT_TO, STOPPED_AT, UNKNOWN
}

Arrival Record

interface ArrivalRecord {
  time: Date;                // Record timestamp
  stop_id: string;           // Stop ID
  route_id: string;          // Route ID
  scheduled_time: Date;      // Scheduled arrival time
  predicted_time?: Date;     // Real-time predicted time
  actual_time?: Date;        // Actual arrival time (if observed)
  delay_seconds: number;     // Delay in seconds
  is_realtime: boolean;      // Based on real-time data
  headsign?: string;         // Trip headsign
}

Error Handling

All endpoints return standard HTTP status codes:

Status Code Description
200 Success
404 Resource not found (e.g., invalid stop/route ID)
500 Server error

Error Response Format:

{
  "error": "Description of the error",
  "details": "Additional error details (when available)"
}

Example Error:

curl "http://localhost:3000/api/config?stopId=invalid"

Response (404):

{
  "error": "Stop invalid not found"
}

Rate Limiting

Currently, there is no rate limiting implemented. For production deployments, consider:

  • Implementing per-IP rate limits
  • Using caching for frequently accessed endpoints
  • Load balancing for high-traffic scenarios

Recommended Polling Intervals:

  • Real-time arrivals: 10-30 seconds
  • Vehicle positions: 10-30 seconds
  • Historical data: As needed (not time-sensitive)
  • Statistics: 5-60 minutes (depending on use case)

GTFS Data Update

The GTFS static data (stops, routes, schedules) is loaded from the gtfs/ directory on server startup.

To update GTFS data:

bun run setup-gtfs.ts

This downloads the latest GTFS feed and extracts it. Restart the server to load the new data.


TimescaleDB Setup

Historical data and analytics require TimescaleDB. See the README.md for setup instructions.

Quick start:

cd infrastructure
docker compose up -d

The database automatically:

  • Creates hypertables for time-series data
  • Sets up 90-day data retention
  • Builds continuous aggregates for hourly metrics
  • Maintains indexes for fast queries

Example Use Cases

Building a Real-Time Tracker

// 1. Get all stops
const stops = await fetch('/api/stops').then(r => r.json());

// 2. Get all routes  
const routes = await fetch('/api/routes').then(r => r.json());

// 3. Poll for arrivals every 15 seconds
setInterval(async () => {
  const arrivals = await fetch('/api/arrivals?stopId=1563&routeId=125')
    .then(r => r.json());
  updateUI(arrivals);
}, 15000);

// 4. Poll for vehicle positions
setInterval(async () => {
  const vehicles = await fetch('/api/vehicles?routeId=125')
    .then(r => r.json());
  updateMap(vehicles);
}, 15000);

Building Analytics Dashboard

// Get delay statistics for route
const stats = await fetch('/api/stats/route/125/delays?hours=168')
  .then(r => r.json());

// Get hourly patterns
const pattern = await fetch('/api/stats/route/125/hourly?days=30')
  .then(r => r.json());

// Get stop performance comparison
const stopStats = await fetch('/api/stats/stop/1563/delays?hours=168')
  .then(r => r.json());

// Display charts with the data
renderDelayChart(stats);
renderHourlyPattern(pattern);
renderStopComparison(stopStats);

Additional Resources


Support

For issues or questions:

  1. Check the README.md for setup instructions
  2. Review this documentation
  3. Check the source code in server.ts and lib/gtfs.ts

Last Updated: February 7, 2026
API Version: 1.0.0