{"openapi":"3.1.0","info":{"title":"IMTF API","description":"Is My Train Fucked? - NYC Subway Status API","version":"1.0.0","contact":{"name":"Chris Becker","url":"https://github.com/Cbeck527/imtf-api"}},"servers":[{"url":"https://api.ismytrainfucked.com","description":"Production server"},{"url":"http://localhost:8787","description":"Local development server"}],"tags":[{"name":"Alerts","description":"Subway status and alerts endpoints"},{"name":"System","description":"System and health check endpoints"}],"components":{"schemas":{"HealthResponse":{"type":"object","properties":{"status":{"type":"string","enum":["ok"]},"timestamp":{"type":"string"},"service":{"type":"string","enum":["imtf-api"]}},"required":["status","timestamp","service"]},"SubwayStatusResponse":{"type":"object","properties":{"trunkLines":{"type":"array","items":{"$ref":"#/components/schemas/TrunkLine"}},"sourceTimestamp":{"type":"string","format":"date-time","description":"Timestamp published in the MTA feed header"},"fetchedAt":{"type":"string","format":"date-time","description":"Time the feed was transformed"},"cacheExpires":{"type":"string","format":"date-time","description":"Time this response should be considered stale"}},"required":["trunkLines","sourceTimestamp","fetchedAt","cacheExpires"]},"TrunkLine":{"type":"object","properties":{"id":{"type":"string","example":"456","description":"Trunk line identifier"},"name":{"type":"string","example":"IRT Lexington Avenue Line","description":"Official trunk line name"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/SubwayLine"}},"status":{"$ref":"#/components/schemas/AlertStatus"}},"required":["id","name","lines","status"]},"SubwayLine":{"type":"object","properties":{"id":{"type":"string","example":"4","description":"Line identifier"},"name":{"type":"string","example":"4","description":"Display name"},"status":{"$ref":"#/components/schemas/AlertStatus"},"activeAlerts":{"type":"array","items":{"$ref":"#/components/schemas/Alert"},"description":"Alerts whose GTFS active period contains the current time"},"upcomingAlerts":{"type":"array","items":{"$ref":"#/components/schemas/Alert"},"description":"Future alerts that are not yet active"}},"required":["id","name","status","activeAlerts","upcomingAlerts"]},"AlertStatus":{"type":"object","properties":{"kind":{"type":"string","enum":["alert","good_service"],"description":"Whether an MTA alert currently determines this status"},"label":{"type":"string","example":"Delays","description":"Exact MTA alert type, or Good Service"},"impactCategory":{"$ref":"#/components/schemas/ImpactCategory"},"disruptionKind":{"$ref":"#/components/schemas/DisruptionKind"},"sortOrder":{"type":"integer","nullable":true,"example":26,"description":"Route-specific MTA Mercury priority"},"sourceAlertId":{"type":"string","nullable":true,"example":"lmm:alert:12345","description":"MTA entity ID that determines this status"}},"required":["kind","label","impactCategory","disruptionKind","sortOrder","sourceAlertId"]},"ImpactCategory":{"type":"string","enum":["delay","service_change","information","none"],"description":"Stable IMTF presentation category"},"DisruptionKind":{"type":"string","enum":["unplanned","planned","none"],"description":"Whether the selected alert is planned, unplanned, or non-disruptive"},"Alert":{"type":"object","properties":{"id":{"type":"string","example":"lmm:alert:12345","description":"Stable MTA feed entity ID"},"alertType":{"type":"string","example":"Delays","description":"Exact MTA Mercury alert type"},"impactCategory":{"$ref":"#/components/schemas/ImpactCategory"},"disruptionKind":{"$ref":"#/components/schemas/DisruptionKind"},"sortOrder":{"type":"integer","nullable":true,"description":"Highest Mercury priority for this route"},"header":{"type":"string"},"description":{"type":"string","nullable":true},"createdAt":{"type":"string","nullable":true,"format":"date-time"},"updatedAt":{"type":"string","nullable":true,"format":"date-time"},"activePeriods":{"type":"array","items":{"$ref":"#/components/schemas/ActivePeriod"}},"displayBeforeActiveSeconds":{"type":"integer","nullable":true,"minimum":0},"affectedEntities":{"type":"array","items":{"$ref":"#/components/schemas/AffectedEntity"}}},"required":["id","alertType","impactCategory","disruptionKind","sortOrder","header","description","createdAt","updatedAt","activePeriods","displayBeforeActiveSeconds","affectedEntities"]},"ActivePeriod":{"type":"object","properties":{"start":{"type":"string","nullable":true,"format":"date-time","description":"Inclusive period start; null means unbounded"},"end":{"type":"string","nullable":true,"format":"date-time","description":"Exclusive period end; null means unbounded"}},"required":["start","end"]},"AffectedEntity":{"type":"object","properties":{"agencyId":{"type":"string","nullable":true},"routeId":{"type":"string","nullable":true},"stopId":{"type":"string","nullable":true},"direction":{"type":"string","nullable":true,"enum":["northbound","southbound",null]},"sortOrder":{"type":"integer","nullable":true}},"required":["agencyId","routeId","stopId","direction","sortOrder"]},"ErrorResponse":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"string"}},"required":["error"]}},"parameters":{}},"paths":{"/health":{"get":{"summary":"Health check","description":"Returns the health status of the API","tags":["System"],"responses":{"200":{"description":"API is healthy","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}}}}},"/v1/alerts":{"get":{"summary":"Get subway status","description":"Returns MTA-derived status plus active and upcoming alerts for every NYC subway line","tags":["Alerts"],"responses":{"200":{"description":"Successful response with exact MTA status and deduplicated alerts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubwayStatusResponse"}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}