# 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://: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>` | Schedule data keyed by park ID and date | | `rideCounts` | `Record` | Number of open rides per park (only for currently operating parks with rides reporting) | | `coasterCounts` | `Record` | 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 } ```