Files
openjsp/API-DOCUMENTATION.md

872 lines
20 KiB
Markdown

# GTFS API Documentation
Comprehensive API documentation for the Skopje Bus Tracker GTFS-based API.
## Table of Contents
- [Overview](#overview)
- [OpenAPI Specification](#openapi-specification)
- [Base URL](#base-url)
- [GTFS Static Data Endpoints](#gtfs-static-data-endpoints)
- [Real-Time Data Endpoints](#real-time-data-endpoints)
- [Historical Data Endpoints](#historical-data-endpoints)
- [Statistics & Analytics Endpoints](#statistics--analytics-endpoints)
- [Data Models](#data-models)
- [Error Handling](#error-handling)
- [Rate Limiting](#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:
- See **[UPSTREAM-API-DOCUMENTATION.md](UPSTREAM-API-DOCUMENTATION.md)**
---
## OpenAPI Specification
A complete **OpenAPI 3.0** specification is available at [openapi.yaml](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):**
```bash
# 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:**
```bash
# 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:**
```json
{
"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:**
```bash
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:**
```json
[
{
"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:**
```bash
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:**
```json
[
{
"id": "125",
"shortName": "7",
"longName": "Centar - GTC - Karpos 4 - Karposh 2"
},
{
"id": "128",
"shortName": "2",
"longName": "Kisela Voda - Centar - 11 Oktomvri - Avtokomanda"
}
]
```
**Example:**
```bash
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:**
```json
[
{
"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:**
```bash
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:**
```json
[
{
"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:**
```bash
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:**
```json
{
"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:**
```bash
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:**
```json
[
{
"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:**
```bash
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:**
```json
[
{
"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:**
```bash
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:**
```json
[
{
"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:**
```bash
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:**
```json
{
"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:**
```bash
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:**
```json
{
"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:**
```bash
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:**
```json
{
"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:**
```bash
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
```typescript
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
```typescript
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
```typescript
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
```typescript
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:**
```json
{
"error": "Description of the error",
"details": "Additional error details (when available)"
}
```
**Example Error:**
```bash
curl "http://localhost:3000/api/config?stopId=invalid"
```
Response (404):
```json
{
"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:**
```bash
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](README.md) for setup instructions.
**Quick start:**
```bash
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
```javascript
// 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
```javascript
// 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](openapi.yaml) - Machine-readable API spec
- **Upstream API Documentation**: [UPSTREAM-API-DOCUMENTATION.md](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](README.md)
---
## Support
For issues or questions:
1. Check the [README.md](README.md) for setup instructions
2. Review this documentation
3. Check the source code in [server.ts](server.ts) and [lib/gtfs.ts](lib/gtfs.ts)
---
**Last Updated:** February 7, 2026
**API Version:** 1.0.0