Add docs/ folder with architecture, operations, API reference, and development guides covering system design, deployment, troubleshooting, all backend endpoints, and contributor workflows. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
12 KiB
API Reference
See also: Architecture | Operations | Development
Base URL
| Context | URL |
|---|---|
| Local development | http://localhost:3001 |
| Docker internal (web → backend) | http://backend:3001 |
| External access (via host port) | http://<host>:3001 |
Authentication
None. All endpoints are public and unauthenticated. The scrape trigger endpoint is also unprotected -- restrict access at the network/proxy level if needed.
Endpoints
GET /api/calendar/week
Returns a 7-day calendar for all parks, starting from the given Sunday.
Query Parameters:
| Param | Required | Format | Description |
|---|---|---|---|
start |
Yes | YYYY-MM-DD |
Week start date (should be a Sunday) |
Cache: Cache-Control: public, max-age=120, stale-while-revalidate=300
Response:
{
"weekStart": "2026-04-19",
"weekDates": ["2026-04-19", "2026-04-20", "2026-04-21", "2026-04-22", "2026-04-23", "2026-04-24", "2026-04-25"],
"today": "2026-04-23",
"isCurrentWeek": true,
"data": {
"cedarpoint": {
"2026-04-23": {
"isOpen": true,
"hoursLabel": "10am – 8pm",
"specialType": null
},
"2026-04-24": {
"isOpen": false,
"hoursLabel": null,
"specialType": null
}
}
},
"rideCounts": {
"cedarpoint": 42,
"greatadventure": 38
},
"coasterCounts": {
"cedarpoint": 12,
"greatadventure": 9
},
"openParkIds": ["cedarpoint", "greatadventure"],
"closingParkIds": [],
"weatherDelayParkIds": [],
"hasCoasterData": true,
"scrapedCount": 168
}
Response fields:
| Field | Type | Description |
|---|---|---|
weekStart |
string |
Echo of the start parameter |
weekDates |
string[] |
Array of 7 date strings (Sun-Sat) |
today |
string |
Current date (with 3 AM switchover) |
isCurrentWeek |
boolean |
Whether this week contains today |
data |
Record<parkId, Record<date, DayData>> |
Schedule data keyed by park ID and date |
rideCounts |
Record<parkId, number> |
Number of open rides per park (only for currently operating parks with rides reporting) |
coasterCounts |
Record<parkId, number> |
Number of open coasters per park |
openParkIds |
string[] |
Parks currently within their operating window |
closingParkIds |
string[] |
Parks past close but within 1-hour wind-down |
weatherDelayParkIds |
string[] |
Parks within window but all rides closed |
hasCoasterData |
boolean |
Always true (coaster data is static) |
scrapedCount |
number |
Total day records returned (sanity check for empty database) |
Errors:
| Status | Body | Condition |
|---|---|---|
| 400 | { "error": "Missing or invalid ?start=YYYY-MM-DD" } |
Missing or malformed start parameter |
GET /api/calendar/:parkId/month
Returns a month calendar for a single park.
Path Parameters:
| Param | Description |
|---|---|
parkId |
Park identifier (e.g. cedarpoint, greatadventure) |
Query Parameters:
| Param | Required | Format | Description |
|---|---|---|---|
month |
Yes | YYYY-MM |
Month to fetch |
Cache: Cache-Control: public, max-age=300, stale-while-revalidate=600
Response:
{
"parkId": "cedarpoint",
"year": 2026,
"month": 6,
"monthData": {
"2026-06-01": {
"isOpen": true,
"hoursLabel": "10am – 10pm",
"specialType": null
},
"2026-06-02": {
"isOpen": true,
"hoursLabel": "10am – 8pm",
"specialType": null
}
},
"today": "2026-04-23"
}
If viewing the current month, today's data is fetched live from the Six Flags API and merged into the response.
Errors:
| Status | Body | Condition |
|---|---|---|
| 400 | { "error": "Missing or invalid ?month=YYYY-MM" } |
Missing or malformed month parameter |
| 400 | { "error": "Month must be 1-12" } |
Month value out of range |
GET /api/parks
Returns metadata for all 24 parks.
Cache: Cache-Control: public, max-age=3600
Response:
{
"parks": [
{
"id": "cedarpoint",
"apiId": 70,
"name": "Cedar Point",
"shortName": "Cedar Point",
"chain": "sixflags",
"slug": "cedarpoint",
"region": "Midwest",
"location": {
"lat": 41.4784,
"lng": -82.6834,
"city": "Sandusky",
"state": "OH"
},
"timezone": "America/New_York",
"website": "https://www.sixflags.com"
}
]
}
GET /api/parks/:id
Returns metadata for a single park.
Path Parameters:
| Param | Description |
|---|---|
id |
Park identifier |
Cache: Cache-Control: public, max-age=3600
Response: A single Park object (same shape as one element of the /api/parks array).
Errors:
| Status | Body | Condition |
|---|---|---|
| 404 | { "error": "Park not found" } |
Unknown park ID |
GET /api/parks/:id/rides
Returns live ride status or schedule fallback for a park.
Path Parameters:
| Param | Description |
|---|---|
id |
Park identifier |
Cache: Cache-Control: public, max-age=60, stale-while-revalidate=120
Response:
{
"parkId": "cedarpoint",
"today": "2026-04-23",
"parkOpenToday": true,
"withinWindow": true,
"isWeatherDelay": false,
"liveRides": {
"rides": [
{
"name": "Steel Vengeance",
"isOpen": true,
"waitMinutes": 45,
"lastUpdated": "2026-04-23T18:30:00.000Z",
"isCoaster": true
},
{
"name": "Millennium Force",
"isOpen": false,
"waitMinutes": 0,
"lastUpdated": "2026-04-23T18:30:00.000Z",
"isCoaster": true
}
],
"fetchedAt": "2026-04-23T18:35:00.000Z"
},
"scheduleFallback": null
}
Response fields:
| Field | Type | Description |
|---|---|---|
parkId |
string |
Echo of the park ID |
today |
string |
Current date (with 3 AM switchover) |
parkOpenToday |
boolean |
Whether the park has hours scheduled today |
withinWindow |
boolean |
Whether current time is within operating hours |
isWeatherDelay |
boolean |
Park is open but all rides are closed |
liveRides |
LiveRidesResult | null |
Queue-Times live data (null if park has no mapping or is outside window) |
scheduleFallback |
RidesFetchResult | null |
Six Flags schedule data (only populated when liveRides is null) |
Data priority:
- If a Queue-Times mapping exists and the park is tracked,
liveRidesis populated. - If outside the operating window, all rides in
liveRidesare forced toisOpen: false. - If no live data is available,
scheduleFallbackis fetched from the Six Flags schedule API for the nearest open date.
Errors:
| Status | Body | Condition |
|---|---|---|
| 404 | { "error": "Park not found" } |
Unknown park ID |
GET /api/status
Health check endpoint with database statistics.
Response:
{
"status": "ok",
"uptime": 86400,
"parks": 24,
"database": {
"totalDays": 8760,
"lastScrape": "2026-04-23T14:00:12.000Z"
},
"lastScrapeResult": {
"scope": "today",
"fetched": 24,
"skipped": 0,
"errors": 0,
"updated": 3,
"startedAt": "2026-04-23T14:00:00.000Z",
"finishedAt": "2026-04-23T14:00:12.000Z"
}
}
| Field | Type | Description |
|---|---|---|
status |
string |
Always "ok" |
uptime |
number |
Process uptime in seconds |
parks |
number |
Number of tracked parks (24) |
database.totalDays |
number |
Total rows in park_days table |
database.lastScrape |
string | null |
ISO timestamp of the most recent scraped_at value |
lastScrapeResult |
ScrapeResult | null |
Result of the last completed scrape (null if none has run yet) |
POST /api/scrape/trigger
Manually triggers a data scrape. See Operations > Manual Scraping for detailed usage.
Query Parameters:
| Param | Required | Default | Values | Description |
|---|---|---|---|---|
scope |
No | today |
today, month, upcoming, full, force |
What to scrape |
Scope details:
| Scope | What it scrapes | Staleness check | Inter-park delay |
|---|---|---|---|
today |
Today's hours only | No (uses diff-before-write) | 500ms |
month |
Current month | Yes (skips if fresh) | 1000ms |
upcoming |
Current + next month | Yes | 1000ms |
full |
All 12 months | Yes | 1000ms |
force |
All 12 months | No (ignores staleness) | 1000ms |
Response:
{
"scope": "today",
"fetched": 24,
"skipped": 0,
"errors": 0,
"updated": 3,
"startedAt": "2026-04-23T14:00:00.000Z",
"finishedAt": "2026-04-23T14:00:12.000Z"
}
Errors:
| Status | Body | Condition |
|---|---|---|
| 400 | { "error": "Invalid scope. Use: today, month, upcoming, full, force" } |
Unknown scope value |
Data Types
DayData
Core schedule data for a single park on a single day.
interface DayData {
isOpen: boolean; // Whether the park is open
hoursLabel: string | null; // e.g. "10am – 6pm", null when closed
specialType: string | null; // "passholder_preview" or null
}
Park
Park metadata (static, defined in lib/parks.ts).
interface Park {
id: string; // Unique identifier (e.g. "cedarpoint")
apiId: number; // Six Flags CloudFront API park ID
name: string; // Full display name
shortName: string; // Abbreviated name
chain: string; // "sixflags"
slug: string; // URL-safe slug
region: string; // Geographic region
location: {
lat: number;
lng: number;
city: string;
state: string;
};
timezone: string; // IANA timezone (e.g. "America/New_York")
website: string; // Park website URL
}
LiveRide
A single ride from the Queue-Times.com API.
interface LiveRide {
name: string; // Ride display name
isOpen: boolean; // Currently operating
waitMinutes: number; // Current wait time (0 if closed)
lastUpdated: string; // ISO 8601 timestamp from Queue-Times
isCoaster: boolean; // Classified as a roller coaster via RCDB data
}
LiveRidesResult
Container for live ride data.
interface LiveRidesResult {
rides: LiveRide[]; // All rides, sorted: open first, then alphabetical
fetchedAt: string; // ISO timestamp of when we fetched from Queue-Times
}
RidesFetchResult
Schedule-based ride data (fallback when live data is unavailable).
interface RidesFetchResult {
rides: RideStatus[]; // Rides with scheduled open/close times
dataDate: string; // YYYY-MM-DD the data came from (may differ from requested date)
isExact: boolean; // true if dataDate matches requested date
parkHoursLabel?: string; // Park-level hours for the data date
}
interface RideStatus {
name: string;
isOpen: boolean; // Has scheduled operating hours
hoursLabel?: string; // e.g. "10am – 10pm"
}
ScrapeResult
Result of a scraping operation.
interface ScrapeResult {
scope: string; // What was scraped (e.g. "today", "months(2026-04)")
fetched: number; // API calls made successfully
skipped: number; // Skipped due to staleness or null response
errors: number; // Failed API calls
updated: number; // Database rows written
startedAt: string; // ISO timestamp
finishedAt: string; // ISO timestamp
}