{
  "openapi": "3.1.0",
  "info": {
    "title": "ThaiSkyward public forecast feeds",
    "version": "1.0.0",
    "description": "Cached model forecasts for Chanthaburi, Thailand. Times use ISO 8601; display in Asia/Bangkok. Null or unavailable values are not zero. These point feeds are separate from the ECMWF/GFS map grid. Public reads never trigger an upstream model fetch."
  },
  "servers": [
    {
      "url": "https://thaiskyward.com"
    }
  ],
  "security": [],
  "externalDocs": {
    "url": "https://thaiskyward.com/api-docs"
  },
  "paths": {
    "/forecast-data.json": {
      "get": {
        "operationId": "readHourlyForecast",
        "summary": "Read cached hourly forecasts, up to 72 hours ahead",
        "parameters": [
          {
            "name": "source",
            "in": "query",
            "description": "Always specify the source when citing data. The map UI defaults to ECMWF, while this feed retains WeatherNext3 as its API default.",
            "schema": {
              "type": "string",
              "enum": [
                "WeatherNext3",
                "ECMWF",
                "GFS"
              ],
              "default": "WeatherNext3"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cache read succeeded; check status and each series fresh flag.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HourlyFeed"
                }
              }
            }
          },
          "400": {
            "description": "Unsupported source.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Cache unavailable; retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/weather-data.json": {
      "get": {
        "operationId": "readDistrictSummary",
        "summary": "Read ECMWF summaries at four representative district coordinates",
        "responses": {
          "200": {
            "description": "Read succeeded; check stale and dataUpdatedAt.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DistrictFeed"
                }
              }
            }
          },
          "503": {
            "description": "No usable district data; retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "HourlyFeed": {
        "type": "object",
        "required": [
          "schemaVersion",
          "source",
          "status",
          "checkedAt",
          "displayTimeZone",
          "freshnessHours",
          "accumulation",
          "units",
          "series"
        ],
        "properties": {
          "schemaVersion": {
            "const": 1
          },
          "source": {
            "enum": [
              "ECMWF",
              "GFS",
              "WeatherNext3"
            ]
          },
          "status": {
            "enum": [
              "ready",
              "unavailable"
            ],
            "description": "ready requires at least one fresh series with hours; unavailable does not mean zero rainfall."
          },
          "checkedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Cache read time, not model issue time."
          },
          "displayTimeZone": {
            "const": "Asia/Bangkok"
          },
          "freshnessHours": {
            "type": "number"
          },
          "accumulation": {
            "type": "string",
            "description": "Rain and ET0 cover the hour ending at validTime."
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "series": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ForecastSeries"
            }
          }
        }
      },
      "ForecastSeries": {
        "type": "object",
        "required": [
          "provider",
          "issuedAt",
          "fetchedAt",
          "location",
          "locationLabel",
          "spatialResolutionKm",
          "fresh",
          "hours"
        ],
        "properties": {
          "provider": {
            "enum": [
              "ECMWF",
              "GFS",
              "WeatherNext 3"
            ]
          },
          "issuedAt": {
            "type": "string",
            "format": "date-time"
          },
          "fetchedAt": {
            "type": "string",
            "format": "date-time"
          },
          "location": {
            "type": "object",
            "required": [
              "lat",
              "lon"
            ],
            "properties": {
              "lat": {
                "type": "number"
              },
              "lon": {
                "type": "number"
              }
            }
          },
          "locationLabel": {
            "type": "string"
          },
          "spatialResolutionKm": {
            "type": "number"
          },
          "fresh": {
            "type": "boolean"
          },
          "hours": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/HourlyForecast"
            }
          }
        }
      },
      "HourlyForecast": {
        "type": "object",
        "required": [
          "validTime",
          "temperature",
          "humidity",
          "rain",
          "wind",
          "gust",
          "et0",
          "vpd"
        ],
        "properties": {
          "validTime": {
            "type": "string",
            "format": "date-time"
          },
          "temperature": {
            "type": [
              "number",
              "null"
            ]
          },
          "humidity": {
            "type": [
              "number",
              "null"
            ]
          },
          "rain": {
            "type": [
              "number",
              "null"
            ]
          },
          "wind": {
            "type": [
              "number",
              "null"
            ]
          },
          "gust": {
            "type": [
              "number",
              "null"
            ]
          },
          "et0": {
            "type": [
              "number",
              "null"
            ]
          },
          "vpd": {
            "type": [
              "number",
              "null"
            ]
          },
          "windDirection": {
            "type": [
              "number",
              "null"
            ]
          },
          "rainP10": {
            "type": [
              "number",
              "null"
            ]
          },
          "rainP90": {
            "type": [
              "number",
              "null"
            ]
          }
        }
      },
      "DistrictFeed": {
        "type": "object",
        "required": [
          "name",
          "source",
          "dataUpdatedAt",
          "stale",
          "staleAfterHours",
          "units",
          "locations"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "canonicalUrl": {
            "type": "string",
            "format": "uri"
          },
          "humanReadableUrl": {
            "type": "string",
            "format": "uri"
          },
          "source": {
            "const": "ECMWF"
          },
          "dataUpdatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "stale": {
            "type": "boolean"
          },
          "staleAfterHours": {
            "type": "number"
          },
          "coverage": {
            "type": "string"
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "locations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DistrictLocation"
            }
          },
          "disclaimer": {
            "type": "string"
          }
        }
      },
      "DistrictLocation": {
        "type": "object",
        "required": [
          "district",
          "canonicalUrl",
          "latitude",
          "longitude",
          "forecastValidAt",
          "temperature",
          "humidity",
          "windSpeed",
          "rainfall"
        ],
        "properties": {
          "district": {
            "type": "string"
          },
          "canonicalUrl": {
            "type": "string"
          },
          "forecastValidAt": {
            "type": "string"
          },
          "latitude": {
            "type": "number"
          },
          "longitude": {
            "type": "number"
          },
          "temperature": {
            "type": "number"
          },
          "humidity": {
            "type": "number"
          },
          "windSpeed": {
            "type": "number"
          },
          "rainfall": {
            "type": "number"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          }
        }
      }
    }
  }
}
