Files
SixFlagsSuperCalendar/docs/API.md
T
josh a53e3ffa9f
Build and Deploy / Build & Push (push) Successful in 1m31s
docs: add comprehensive project documentation
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>
2026-04-23 22:15:02 -04:00

450 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# API Reference
> See also: [Architecture](ARCHITECTURE.md) | [Operations](OPERATIONS.md) | [Development](DEVELOPMENT.md)
## 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:**
```json
{
"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:**
```json
{
"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:**
```json
{
"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:**
```json
{
"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:**
1. If a Queue-Times mapping exists and the park is tracked, `liveRides` is populated.
2. If outside the operating window, all rides in `liveRides` are forced to `isOpen: false`.
3. If no live data is available, `scheduleFallback` is 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:**
```json
{
"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](OPERATIONS.md#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:**
```json
{
"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.
```typescript
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`).
```typescript
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.
```typescript
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.
```typescript
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).
```typescript
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.
```typescript
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
}
```