20 KiB
GTFS API Documentation
Comprehensive API documentation for the Skopje Bus Tracker GTFS-based API.
Table of Contents
- Overview
- OpenAPI Specification
- Base URL
- GTFS Static Data Endpoints
- Real-Time Data Endpoints
- Historical Data Endpoints
- Statistics & Analytics Endpoints
- Data Models
- Error Handling
- Rate Limiting
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 busisRealtime: Whether prediction is based on real-time datarealtimeState:SCHEDULED,UPDATED, orCANCELED
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 IDvehicleId: Vehicle identification number (license plate)label: Bus inventory number (shown on bus)lat/lon: Current GPS coordinatesbearing: Direction of travel (0-360 degrees, 0=North)speed: Current speed in km/htimestamp: Last position update timetripId: GTFS trip ID vehicle is currently servingcurrentStatus:IN_TRANSIT_TO,STOPPED_AT, orUNKNOWN
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
- OpenAPI Specification: openapi.yaml - Machine-readable API spec
- Upstream API Documentation: UPSTREAM-API-DOCUMENTATION.md - ModeShift GTFS API docs
- Swagger UI Demo: https://editor.swagger.io/ (paste openapi.yaml content)
- GTFS Specification: https://gtfs.org/
- GTFS-RT Reference: https://gtfs.org/realtime/
- TimescaleDB Documentation: https://docs.timescale.com/
- Project README: README.md
Support
For issues or questions:
- Check the README.md for setup instructions
- Review this documentation
- Check the source code in server.ts and lib/gtfs.ts
Last Updated: February 7, 2026
API Version: 1.0.0