{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://gamedayweather.app/api/board.schema.json","title":"Game Day Weather public board","description":"Read-only NFL kickoff-window weather board. Served at /api/board.json (current week) and /api/board/{weekId}.json (e.g. /api/board/2026-w04.json). `call` is the Game Day Weather judgment for each game; the chip and hourly readings are supporting data. No colorStat, Comp%, or historical stats.","type":"object","additionalProperties":false,"required":["schemaVersion","season","week","weekId","isCurrentWeek","url","apiUrl","asOf","asOfEtLabel","generatedAt","stale","nextUpdate","counts","rules","sources","attribution","games"],"properties":{"schemaVersion":{"type":"string","const":"1.0"},"season":{"type":"integer","minimum":2026},"week":{"type":"integer","minimum":1,"maximum":18},"weekId":{"type":"string","pattern":"^[0-9]{4}-w[0-9]{2}$","examples":["2026-w04"]},"isCurrentWeek":{"type":"boolean","description":"True when this is the active week on the homepage."},"url":{"type":"string","format":"uri","description":"Human page for this board."},"apiUrl":{"type":"string","format":"uri","description":"Canonical per-week JSON URL."},"asOf":{"$ref":"#/$defs/utcOrNull","description":"When Sideline last posted this board (newest row asOf). Null for a schedule-only week."},"asOfEtLabel":{"type":["string","null"],"examples":["Mon 3:00pm ET"],"description":"asOf in plain Eastern Time, same wording as the site footer."},"generatedAt":{"$ref":"#/$defs/utc","description":"When this response was built."},"stale":{"type":"boolean","description":"True when a scheduled update for an unfinished week passed more than 90 minutes ago and the board has not been refreshed since."},"nextUpdate":{"type":["object","null"],"additionalProperties":false,"required":["at","label","appliesTo"],"properties":{"at":{"$ref":"#/$defs/utc"},"label":{"type":"string","examples":["Thu 3pm ET","Sun 9am ET","Sun 12pm ET","Mon 3pm ET"]},"appliesTo":{"enum":["this-week","next-week"],"description":"next-week when this week has no more scheduled posts before its last kickoff."}}},"counts":{"type":"object","additionalProperties":false,"required":["FLAG","WATCH","CLEAR","DOME"],"description":"Games per `call`. Matches the site header chips.","properties":{"FLAG":{"type":"integer","minimum":0},"WATCH":{"type":"integer","minimum":0},"CLEAR":{"type":"integer","minimum":0},"DOME":{"type":"integer","minimum":0}}},"rules":{"type":"object","additionalProperties":false,"required":["callField","chipWindow","chipThresholds","units","updateSchedule"],"properties":{"callField":{"const":"call"},"chipWindow":{"type":"object","additionalProperties":false,"required":["fromOffsetH","toOffsetH","relativeTo"],"properties":{"fromOffsetH":{"const":-2},"toOffsetH":{"const":2},"relativeTo":{"const":"kickoff"}}},"chipThresholds":{"type":"object","additionalProperties":false,"required":["popPct","windMph","lowTempF","wetField"],"properties":{"popPct":{"$ref":"#/$defs/threshold"},"windMph":{"$ref":"#/$defs/threshold"},"lowTempF":{"$ref":"#/$defs/threshold"},"wetField":{"type":"object","additionalProperties":false,"required":["WATCH","FLAG"],"properties":{"WATCH":{"const":"moderate"},"FLAG":{"const":"high"}}}}},"units":{"type":"object","additionalProperties":false,"required":["temp","wind","pop","precip","time"],"properties":{"temp":{"const":"°F"},"wind":{"const":"mph"},"pop":{"const":"%"},"precip":{"const":"in"},"time":{"type":"string"}}},"updateSchedule":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["label","conditional"],"properties":{"label":{"type":"string"},"conditional":{"type":"boolean"}}}}}},"sources":{"type":"array","minItems":1,"items":{"type":"object","additionalProperties":false,"required":["id","name","role","url"],"properties":{"id":{"enum":["nws","nws-products","open-meteo","met.no","club-roof"]},"name":{"type":"string"},"role":{"type":"string"},"url":{"type":["string","null"],"format":"uri"}}}},"attribution":{"type":"object","additionalProperties":false,"required":["name","url","citeAs","terms","notAffiliated","disclaimer"],"properties":{"name":{"const":"Game Day Weather"},"url":{"const":"https://gamedayweather.app"},"citeAs":{"type":"string"},"terms":{"type":"string"},"notAffiliated":{"type":"string"},"disclaimer":{"type":"string"}}},"changesSinceThursday":{"type":"array","description":"Call changes and retractable-roof announcements since the first Thursday board. Newest first. Empty before that snapshot or when nothing has changed. Does not change call. Schema version stays 1.0.","items":{"type":"object","additionalProperties":false,"required":["kind","gameId","team","at","label"],"properties":{"kind":{"enum":["call","roof"]},"gameId":{"type":"string"},"team":{"type":"string","description":"Home team code. The public line leads with this."},"at":{"$ref":"#/$defs/utc"},"label":{"type":"string","examples":["DAL roof closed (Cowboys, 10:41am ET)","BUF WATCH → FLAG"]},"from":{"$ref":"#/$defs/call"},"to":{"$ref":"#/$defs/call"},"status":{"enum":["open","closed","TBD"]},"teamName":{"type":"string"},"source":{"type":"string","format":"uri"},"announcedAt":{"$ref":"#/$defs/utc"}},"allOf":[{"if":{"properties":{"kind":{"const":"call"}},"required":["kind"]},"then":{"required":["from","to"]}},{"if":{"properties":{"kind":{"const":"roof"}},"required":["kind"]},"then":{"required":["status","teamName","source","announcedAt"]}}]}},"games":{"type":"array","items":{"$ref":"#/$defs/game"}}},"$defs":{"utc":{"type":"string","format":"date-time","pattern":"Z$"},"utcOrNull":{"anyOf":[{"$ref":"#/$defs/utc"},{"type":"null"}]},"call":{"enum":["FLAG","WATCH","CLEAR","DOME"]},"lean":{"enum":["FLAG","WATCH","CLEAR"]},"threshold":{"type":"object","additionalProperties":false,"required":["WATCH","FLAG","compare"],"properties":{"WATCH":{"type":"number"},"FLAG":{"type":"number"},"compare":{"enum":[">=","<="]}}},"wetField":{"enum":["none","low","moderate","high"]},"hour":{"type":"object","additionalProperties":false,"required":["offsetH","timeUtc","tempF","windMph","popPct","sky","inChipWindow"],"properties":{"offsetH":{"type":"integer","description":"Hours from kickoff. 0 = kick hour."},"timeUtc":{"$ref":"#/$defs/utc","description":"kickoffUtc + offsetH."},"tempF":{"type":"number"},"windMph":{"type":"number","minimum":0,"description":"Sustained wind."},"popPct":{"type":"number","minimum":0,"maximum":100,"description":"Probability of precipitation."},"sky":{"type":"string","description":"Source wording, passed through (NWS short forecast, or met.no symbol for international games)."},"inChipWindow":{"type":"boolean","description":"True when offsetH is between -2 and +2."}}},"trigger":{"type":"object","additionalProperties":false,"required":["metric","value","level"],"properties":{"metric":{"enum":["popPct","windMph","lowTempF","wetField"]},"value":{"type":["number","string"]},"level":{"enum":["WATCH","FLAG"]}}},"chip":{"type":"object","additionalProperties":false,"required":["level","maxPopPct","maxWindMph","lowTempF","wetField","triggers","afterTip"],"description":"What the site's chip rules say from kick±2h readings plus wetField. Supporting data, not the call.","properties":{"level":{"$ref":"#/$defs/call"},"maxPopPct":{"type":["number","null"]},"maxWindMph":{"type":["number","null"]},"lowTempF":{"type":["number","null"]},"wetField":{"$ref":"#/$defs/wetField"},"triggers":{"type":"array","items":{"$ref":"#/$defs/trigger"}},"afterTip":{"type":["object","null"],"additionalProperties":false,"required":["label","metric","value","level"],"description":"Hours after kick+2h that would have raised the chip on their own. Quiet tip only; does not change chip or call.","properties":{"label":{"type":"string","examples":["after: 29%","after: 15 mph"]},"metric":{"enum":["popPct","windMph","lowTempF"]},"value":{"type":"number"},"level":{"enum":["WATCH","FLAG"]}}}}},"game":{"type":"object","additionalProperties":false,"required":["gameId","url","kickoffUtc","kickoffEt","kickoffEtLabel","kickoffTbd","away","home","neutralSite","network","stadium","roof","windowStatus","call","callSource","callMatchesChip","confidence","note","chip","field","hours","hourlySource","asOf","nextUpdate"],"properties":{"gameId":{"type":"string","pattern":"^[0-9]{4}-w[0-9]{2}-[a-z]{2,3}-[a-z]{2,3}$","examples":["2026-w04-ind-was"],"description":"Lowercase team codes, away-home. Washington is `was`."},"url":{"type":"string","format":"uri"},"kickoffUtc":{"$ref":"#/$defs/utc"},"kickoffEt":{"type":"string","format":"date-time","pattern":"-0[45]:00$","description":"Kickoff in America/New_York with offset."},"kickoffEtLabel":{"type":"string","examples":["Sun 1:00pm ET","TBD ET"]},"kickoffTbd":{"type":"boolean"},"away":{"type":"string","pattern":"^[A-Z]{2,3}$"},"home":{"type":"string","pattern":"^[A-Z]{2,3}$"},"neutralSite":{"type":"boolean"},"network":{"type":["string","null"]},"stadium":{"type":"object","additionalProperties":false,"required":["id","name","city","tz","country","international","surface"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"city":{"type":"string"},"tz":{"type":"string"},"country":{"type":"string"},"international":{"type":"boolean"},"surface":{"enum":["grass","turf"]}}},"roof":{"type":"object","additionalProperties":false,"required":["type","state","outdoorLean"],"properties":{"type":{"enum":["outdoor","fixed","retractable"]},"state":{"enum":["open","closed","tbd"]},"outdoorLean":{"anyOf":[{"$ref":"#/$defs/lean"},{"type":"null"}],"description":"Retractable only: the call if the roof is open."},"status":{"enum":["open","closed","TBD"],"description":"Club announcement for a retractable roof. Omitted on outdoor and fixed roofs. Default TBD when nobody has posted. Does not change call."},"source":{"type":["string","null"],"format":"uri","description":"Link Sideline cited. Null when status is the TBD default."},"announcedAt":{"anyOf":[{"$ref":"#/$defs/utc"},{"type":"null"}],"description":"When the club announced this status. Null when status is the TBD default."}},"allOf":[{"if":{"properties":{"type":{"const":"fixed"}}},"then":{"properties":{"state":{"const":"closed"},"outdoorLean":{"type":"null"}}}},{"if":{"properties":{"type":{"const":"outdoor"}}},"then":{"properties":{"state":{"const":"open"},"outdoorLean":{"type":"null"}}}}]},"windowStatus":{"enum":["upcoming","live","final"]},"call":{"anyOf":[{"$ref":"#/$defs/call"},{"type":"null"}],"description":"THE call. Null only when no forecast is posted yet (schedule preview)."},"callSource":{"enum":["sideline","chip",null],"description":"sideline = posted judgment. chip = row had no posted call, so the chip level is shown (site fallback)."},"callMatchesChip":{"type":["boolean","null"]},"confidence":{"enum":["HIGH","MED","LOW",null]},"note":{"type":["string","null"],"description":"Why the call is what it is. Explains any disagreement with the chip."},"chip":{"anyOf":[{"$ref":"#/$defs/chip"},{"type":"null"}]},"field":{"type":["object","null"],"additionalProperties":false,"required":["wetField","precipLast36hIn"],"properties":{"wetField":{"$ref":"#/$defs/wetField"},"precipLast36hIn":{"type":"number","minimum":0}}},"hours":{"type":"array","items":{"$ref":"#/$defs/hour"},"description":"Hourly readings from kickoff, as posted. Empty for fixed domes and schedule-only games."},"hourlySource":{"enum":["nws","met.no",null]},"asOf":{"$ref":"#/$defs/utcOrNull"},"nextUpdate":{"type":["object","null"],"additionalProperties":false,"required":["at","label"],"description":"Next scheduled post before this game's kickoff. Null when the game is final, a fixed dome, or no post is scheduled before kick.","properties":{"at":{"$ref":"#/$defs/utc"},"label":{"type":"string"}}}},"allOf":[{"if":{"properties":{"call":{"$ref":"#/$defs/call"}},"required":["call"]},"then":{"properties":{"callSource":{"enum":["sideline","chip"]},"callMatchesChip":{"type":"boolean"},"confidence":{"enum":["HIGH","MED","LOW"]},"note":{"type":"string"},"chip":{"$ref":"#/$defs/chip"},"field":{"type":"object"},"asOf":{"$ref":"#/$defs/utc"}}}},{"if":{"properties":{"callMatchesChip":{"const":false}},"required":["callMatchesChip"]},"then":{"properties":{"note":{"type":"string","minLength":1}}}},{"if":{"properties":{"roof":{"properties":{"type":{"const":"fixed"}}},"call":{"$ref":"#/$defs/call"}},"required":["call"]},"then":{"properties":{"call":{"const":"DOME"},"hours":{"maxItems":0}}}}]}}}