{"openapi":"3.1.0","info":{"title":"Hoops Menu public API","version":"1.1.0","description":"Public, read-only JSON endpoints published by Hoops Menu. All endpoints described here are unauthenticated, support open CORS (`Access-Control-Allow-Origin: *`), and are safe to cache per their `Cache-Control` response headers.","contact":{"url":"https://hoopsmenu.com/data/travel","email":"hello@hoopsmenu.com"},"license":{"name":"CC BY 4.0","url":"https://creativecommons.org/licenses/by/4.0/"}},"servers":[{"url":"https://hoopsmenu.com"}],"paths":{"/data/travel/{leagueSlug}":{"get":{"operationId":"getLeagueTravelData","summary":"Team travel and schedule-density data for a league","description":"Travel measurements (miles traveled, cumulative time-zone shift, back-to-back and dense-schedule counts, plus — since schemaVersion 1.1 — time-zone crossings, rest-disadvantage games, the worst seven-day stretch, and the Schedule Difficulty Index and rank) for every team in a travel-capable league. By default, the endpoint returns the newest scheduled season. Pass `?season=2025-26` to request an available historical season. Backed by the same data as the league's travel board and Toughest Schedule page. The same dataset is available as CSV at `/data/travel/{leagueSlug}/csv`. Public and read-only; responds with open CORS and is cached at the edge (`Cache-Control: public, max-age=0, s-maxage=3600, stale-while-revalidate=86400`).","parameters":[{"name":"leagueSlug","in":"path","required":true,"description":"Canonical league slug. Only leagues the pipeline runs schedule analysis on expose travel data.","schema":{"type":"string","enum":["nba","wnba"]}},{"name":"season","in":"query","required":false,"description":"Season to return, such as 2025-26. If omitted, uses the newest scheduled season for the league. An unavailable season returns 404.","schema":{"type":"string"}}],"responses":{"200":{"description":"Travel data for the requested league.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TravelBoard"}}}},"404":{"description":"Unknown league slug, unavailable season, or the league has no travel dataset (`capabilities.hasTravel` is false).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/data/travel/{leagueSlug}/csv":{"get":{"operationId":"getLeagueTravelDataCsv","summary":"Team travel and schedule-density data for a league (CSV)","description":"The same travel dataset as `/data/travel/{leagueSlug}`, served as a flat CSV — one header row and one row per team, with `worstStretch` and `longestRoadTrip` flattened into prefixed columns (e.g. `worstStretch_miles`, `longestRoadTrip_games`). By default, returns the newest scheduled season; pass `?season=2025-26` for an available historical season. Returned as an attachment (`Content-Disposition: attachment; filename=\"hoops-menu-{slug}-travel-{season}.csv\"`). Public and read-only; responds with open CORS and is cached at the edge (`Cache-Control: public, max-age=0, s-maxage=3600, stale-while-revalidate=86400`).","parameters":[{"name":"leagueSlug","in":"path","required":true,"description":"Canonical league slug. Only leagues the pipeline runs schedule analysis on expose travel data.","schema":{"type":"string","enum":["nba","wnba"]}},{"name":"season","in":"query","required":false,"description":"Season to return, such as 2025-26. If omitted, uses the newest scheduled season for the league. An unavailable season returns 404.","schema":{"type":"string"}}],"responses":{"200":{"description":"Travel data for the requested league, as CSV.","content":{"text/csv":{"schema":{"type":"string"}}}},"404":{"description":"Unknown league slug, unavailable season, or the league has no travel dataset (`capabilities.hasTravel` is false).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/health":{"get":{"operationId":"getHealth","summary":"Service health check","description":"Round-trips a query against the primary database. Not cached (`Cache-Control: no-store`).","responses":{"200":{"description":"The service and its database are reachable.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["ok"]}},"required":["status"]}}}},"503":{"description":"The database is unreachable.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["unavailable"]}},"required":["status"]}}}}}}}},"components":{"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]},"TravelBoard":{"type":"object","description":"Selected-season ('scope': 'season') travel and schedule-density summary for one league.","properties":{"schemaVersion":{"type":"string","description":"Payload schema version. Changes within a major version are additive only (existing field names, shapes, and rounding stay stable); a breaking change bumps the major version.","example":"1.1"},"generatedAt":{"type":"string","format":"date-time","description":"When this response was generated."},"sourceUpdatedAt":{"type":["string","null"],"format":"date-time","description":"When the underlying schedule data was last refreshed."},"league":{"type":"object","properties":{"id":{"type":"string","description":"stats.nba.com league id."},"slug":{"type":"string"},"name":{"type":"string"}},"required":["id","slug","name"]},"season":{"type":"string","example":"2026"},"scope":{"type":"string","enum":["season"]},"methodologyUrl":{"type":"string","format":"uri","description":"Link to the human-readable methodology page."},"license":{"type":"object","description":"Terms the payload is published under. Reuse is free with attribution to Hoops Menu and a link back.","properties":{"name":{"type":"string","example":"CC BY 4.0"},"url":{"type":"string","format":"uri","example":"https://creativecommons.org/licenses/by/4.0/"}},"required":["name","url"]},"units":{"type":"object","description":"Prose definition of each numeric unit below.","properties":{"miles":{"type":"string"},"zoneShiftHours":{"type":"string"},"timeZoneCrossings":{"type":"string"},"restDisadvantageGames":{"type":"string"},"difficultyIndex":{"type":"string"},"scheduleWindows":{"type":"string"}},"required":["miles","zoneShiftHours","timeZoneCrossings","restDisadvantageGames","difficultyIndex","scheduleWindows"]},"teams":{"type":"array","items":{"$ref":"#/components/schemas/TeamTravel"}}},"required":["schemaVersion","generatedAt","sourceUpdatedAt","league","season","scope","methodologyUrl","license","units","teams"]},"TeamTravel":{"type":"object","properties":{"teamId":{"type":"integer"},"slug":{"type":"string"},"name":{"type":"string","description":"City and nickname, e.g. \"New York Liberty\"."},"city":{"type":"string"},"nickname":{"type":"string"},"tricode":{"type":"string","example":"NYL"},"canonicalUrl":{"type":"string","format":"uri","description":"Link to this team's travel page on hoopsmenu.com."},"games":{"type":"integer"},"roadGames":{"type":"integer"},"miles":{"type":"number","description":"Total great-circle miles traveled this season, rounded to one decimal."},"milesPerGame":{"type":["number","null"]},"zoneShiftHours":{"type":"number","description":"Sum of absolute UTC-offset changes between consecutive games."},"backToBacks":{"type":"integer"},"threeInFours":{"type":"integer"},"fourInSixes":{"type":"integer"},"fiveInSevens":{"type":"integer"},"timeZoneCrossings":{"type":["integer","null"],"description":"Added in v1.1. Games whose venue UTC offset differs from the previous game's. Null if the team has no schedule-rankings row."},"restDisadvantageGames":{"type":["integer","null"],"description":"Added in v1.1. Back-to-backs played against an opponent that had at least a day of rest."},"worstStretch":{"type":["object","null"],"description":"Added in v1.1. The team's heaviest seven-day travel window.","properties":{"games":{"type":"integer"},"miles":{"type":"number"},"startDate":{"type":["string","null"],"format":"date"},"endDate":{"type":["string","null"],"format":"date"}},"required":["games","miles","startDate","endDate"]},"difficultyIndex":{"type":["number","null"],"description":"Added in v1.1. Schedule Difficulty Index (0–100): mean of five min-max-normalized metrics. Higher is harder."},"difficultyRank":{"type":["integer","null"],"description":"Added in v1.1. League rank on the Difficulty Index; 1 = toughest schedule."},"longestRoadTrip":{"type":"object","properties":{"games":{"type":["integer","null"]},"miles":{"type":["number","null"]}},"required":["games","miles"]}},"required":["teamId","slug","name","city","nickname","tricode","canonicalUrl","games","roadGames","miles","milesPerGame","zoneShiftHours","backToBacks","threeInFours","fourInSixes","fiveInSevens","timeZoneCrossings","restDisadvantageGames","worstStretch","difficultyIndex","difficultyRank","longestRoadTrip"]}}}}