{"openapi":"3.1.0","info":{"title":"ทันน้ำ flood forecast API","version":"0.1.0","summary":"Thai flood warnings, rain radar nowcasts, rain forecasts and river levels for any point in Thailand.","description":"Read-only JSON API behind the ทันน้ำ web app. Upstream sources are the Thai Meteorological Department (TMD) for radar and weather warnings, and ThaiWater (HII) for rain forecasts and water levels. When an upstream source fails, endpoints still answer 200 with `status: \"unavailable\"` instead of an error. Errors (unknown endpoints, invalid parameters, server faults) are JSON objects with `error.code`, `error.message` and `error.hint`. Times are ISO 8601 strings."},"servers":[{"url":"/"}],"security":[],"paths":{"/api/radar-nowcast":{"get":{"operationId":"getRadarNowcast","summary":"Rain at a point now and for the next two hours, from TMD radar","description":"The first point is the latest radar observation; the rest are S-PROG forecast leads every 15 minutes up to 120 minutes.","parameters":[{"$ref":"#/components/parameters/Latitude"},{"$ref":"#/components/parameters/Longitude"}],"responses":{"200":{"description":"Nowcast, or the reason it is unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RadarNowcast"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/radar-frames":{"get":{"operationId":"getRadarFrames","summary":"Recent observed radar composite images for map overlays","responses":{"200":{"description":"Frame manifest, or the reason it is unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RadarFrameManifest"}}}},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/radar-images/{image}":{"get":{"operationId":"getRadarImage","summary":"One rendered radar frame as a PNG","parameters":[{"name":"image","in":"path","required":true,"description":"File name from a radar-frames entry: the 13-digit observation time in milliseconds plus `.png`.","schema":{"type":"string","pattern":"^\\d{13}\\.png$","maxLength":32},"example":"1791360000000.png"}],"responses":{"200":{"description":"Radar image in Web Mercator, bounded by the manifest's `bounds`.","content":{"image/png":{"schema":{"type":"string","contentMediaType":"image/png"}}}},"404":{"description":"Unknown or expired frame.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Unavailable"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/nowcast-skill":{"get":{"operationId":"getNowcastSkill","summary":"Verification scores of the radar nowcast by lead time","responses":{"200":{"description":"Skill scores, or empty lists when unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NowcastSkill"}}}},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/tmd-warnings":{"get":{"operationId":"getTmdWarnings","summary":"Weather warnings currently in effect from the Thai Meteorological Department","description":"Nationwide CAP alerts; filter by `areaDescription` (Thai province names) to find warnings for a place.","responses":{"200":{"description":"Active warnings, or unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TmdWarnings"}}}},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/water-levels/stations":{"get":{"operationId":"getWaterLevelStations","summary":"All ThaiWater river level stations with a recent reading","responses":{"200":{"description":"Stations, or unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaterLevelMapStations"}}}},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/water-levels":{"get":{"operationId":"getNearestWaterLevels","summary":"River level readings from the stations nearest a point","parameters":[{"$ref":"#/components/parameters/Latitude"},{"$ref":"#/components/parameters/Longitude"}],"responses":{"200":{"description":"Nearest stations, or unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaterLevels"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/rain-forecast":{"get":{"operationId":"getRainForecast","summary":"Hourly rain forecast for a Thai district (อำเภอ/เขต) from ThaiWater","parameters":[{"name":"district","in":"query","required":true,"description":"Four-digit DOPA district code (รหัสอำเภอ), e.g. 1004 for เขตบางรัก. It is the first four digits of a sub-district code.","schema":{"type":"integer","minimum":1000,"maximum":9999},"example":5001}],"responses":{"200":{"description":"Forecast hours, or unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RainForecast"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/openapi.json":{"get":{"operationId":"getOpenApiDocument","summary":"This OpenAPI document (also served at /openapi.json)","responses":{"200":{"description":"OpenAPI 3.1 document.","content":{"application/json":{"schema":{"type":"object"}}}}}}}},"components":{"parameters":{"Latitude":{"name":"lat","in":"query","required":true,"description":"Latitude in decimal degrees (WGS 84).","schema":{"type":"number","minimum":-90,"maximum":90},"example":18.7904},"Longitude":{"name":"long","in":"query","required":true,"description":"Longitude in decimal degrees (WGS 84).","schema":{"type":"number","minimum":-180,"maximum":180},"example":98.9847}},"responses":{"ValidationError":{"description":"A parameter is missing or out of range; `error.details` lists each problem.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalError":{"description":"The server failed; retry later.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message","hint"],"properties":{"code":{"type":"string","enum":["NOT_FOUND","VALIDATION","INTERNAL_SERVER_ERROR"]},"message":{"type":"string"},"hint":{"type":"string"},"details":{"type":"array","items":{"type":"object","required":["path","message"],"properties":{"path":{"type":"string","examples":["/lat"]},"message":{"type":"string"}}}}}}}},"Unavailable":{"type":"object","required":["status"],"properties":{"status":{"const":"unavailable"}}},"RadarNowcast":{"oneOf":[{"type":"object","required":["status","observedAt","source","staleAt","confidence","nearbyRateMmPerHour","points"],"properties":{"status":{"const":"available"},"observedAt":{"type":"string","format":"date-time"},"source":{"enum":["tmd-composite","radargis"],"description":"`radargis` stands in while TMD's composite is down or stale; it runs about 27 minutes behind."},"staleAt":{"type":"string","format":"date-time"},"confidence":{"enum":["high","medium","low"],"description":"high: motion tracked near the point; medium: motion filled from elsewhere; low: no earlier frame, the observation is held in place."},"nearbyRateMmPerHour":{"type":["number","null"]},"points":{"type":"array","items":{"$ref":"#/components/schemas/RadarPoint"}}}},{"type":"object","required":["status","reason"],"properties":{"status":{"const":"unavailable"},"reason":{"enum":["network","malformed","stale","outside_coverage"]}}}]},"RadarPoint":{"type":"object","required":["validAt","rainRateMmPerHour","chance"],"properties":{"validAt":{"type":"string","format":"date-time"},"rainRateMmPerHour":{"type":["number","null"],"description":"Null below TMD's lowest legend bin; not a confirmation of dry weather."},"chance":{"enum":["rain","possible","unlikely"],"description":"rain: at least 1 mm/h at the point; possible: rain within 10 km; unlikely: none within 10 km."}}},"RadarFrameManifest":{"oneOf":[{"type":"object","required":["status","bounds","image","frames","staleAt"],"properties":{"status":{"const":"available"},"bounds":{"type":"object","description":"Web Mercator corner coordinates of every image.","required":["east","north","south","west"],"properties":{"east":{"type":"number"},"north":{"type":"number"},"south":{"type":"number"},"west":{"type":"number"}}},"image":{"type":"object","required":["width","height"],"properties":{"width":{"type":"integer"},"height":{"type":"integer"}}},"frames":{"type":"array","items":{"type":"object","required":["observedAt","url"],"properties":{"observedAt":{"type":"string","format":"date-time"},"url":{"type":"string"}}}},"staleAt":{"type":"string","format":"date-time"}}},{"type":"object","required":["status","reason"],"properties":{"status":{"const":"unavailable"},"reason":{"enum":["network","stale"]}}}]},"LeadSkill":{"type":"object","required":["leadMinutes","samples","meanCsi","meanPersistenceCsi","meanMaeMmPerHour","meanPersistenceMaeMmPerHour"],"properties":{"leadMinutes":{"type":"integer"},"samples":{"type":"integer"},"meanCsi":{"type":["number","null"]},"meanPersistenceCsi":{"type":["number","null"]},"meanMaeMmPerHour":{"type":"number"},"meanPersistenceMaeMmPerHour":{"type":"number"}}},"NowcastSkill":{"type":"object","required":["status","leads","lucasKanade"],"properties":{"status":{"enum":["available","unavailable"]},"leads":{"type":"array","description":"S-PROG, the served forecast.","items":{"$ref":"#/components/schemas/LeadSkill"}},"lucasKanade":{"type":"array","description":"Lucas–Kanade advection baseline.","items":{"$ref":"#/components/schemas/LeadSkill"}}}},"TmdWarnings":{"oneOf":[{"type":"object","required":["status","updatedAt","warnings"],"properties":{"status":{"const":"available"},"updatedAt":{"type":"string","format":"date-time"},"warnings":{"type":"array","items":{"$ref":"#/components/schemas/TmdWarning"}}}},{"$ref":"#/components/schemas/Unavailable"}]},"TmdWarning":{"type":"object","required":["id","event","eventType","headline","description","instruction","severity","urgency","certainty","sentAt","effectiveAt","expiresAt","areaDescription","bulletinUrl","sourceUrl"],"properties":{"id":{"type":"string"},"event":{"type":"string","description":"Thai event name, e.g. ฝนตกหนัก."},"eventType":{"type":"string","description":"CAP event, e.g. Heavy Rain."},"headline":{"type":"string"},"description":{"type":["string","null"]},"instruction":{"type":["string","null"]},"severity":{"type":"string"},"urgency":{"type":"string"},"certainty":{"type":"string"},"sentAt":{"type":"string","format":"date-time"},"effectiveAt":{"type":"string","format":"date-time"},"expiresAt":{"type":"string","format":"date-time"},"areaDescription":{"type":"string","description":"Space-separated Thai province names."},"bulletinUrl":{"type":["string","null"],"format":"uri"},"sourceUrl":{"type":"string","format":"uri","description":"CAP XML the warning was read from."}}},"WaterLevelMapStations":{"oneOf":[{"type":"object","required":["status","stations"],"properties":{"status":{"const":"available"},"stations":{"type":"array","items":{"type":"object","required":["id","name","lat","long","bankFullPercent"],"properties":{"id":{"type":"integer"},"name":{"type":"string"},"lat":{"type":"number"},"long":{"type":"number"},"bankFullPercent":{"type":"number"}}}}}},{"$ref":"#/components/schemas/Unavailable"}]},"WaterLevels":{"oneOf":[{"type":"object","required":["status","stations"],"properties":{"status":{"const":"available"},"stations":{"type":"array","items":{"$ref":"#/components/schemas/WaterLevelStation"}}}},{"$ref":"#/components/schemas/Unavailable"}]},"WaterLevelStation":{"type":"object","required":["id","name","agency","lat","long","distanceKm","observedAt","levelMsl","previousLevelMsl","bankLevelMsl","bankFullPercent"],"properties":{"id":{"type":"integer"},"name":{"type":"string","description":"Thai station name."},"agency":{"type":"string","description":"Short agency name, e.g. RID."},"lat":{"type":"number"},"long":{"type":"number"},"distanceKm":{"type":"number"},"observedAt":{"type":"string","format":"date-time"},"levelMsl":{"type":"number","description":"Water level in metres above mean sea level."},"previousLevelMsl":{"type":["number","null"]},"bankLevelMsl":{"type":"number","description":"Lower riverbank in metres above mean sea level."},"bankFullPercent":{"type":"number","description":"Channel fill from riverbed to the lower bank; above 100 means overflowing."}}},"RainForecast":{"oneOf":[{"type":"object","required":["status","issuedAt","hours"],"properties":{"status":{"const":"available"},"issuedAt":{"type":"string","format":"date-time"},"hours":{"type":"array","items":{"type":"object","required":["at","rainMm"],"properties":{"at":{"type":"string","format":"date-time"},"rainMm":{"type":"number","minimum":0}}}}}},{"$ref":"#/components/schemas/Unavailable"}]}}}}