{"openapi":"3.1.0","info":{"title":"weather-mcp","version":"0.1.0","description":"The HTTP API of a Swiss weather resource server. It is a sibling of the MCP endpoint at /mcp rather than a translation of it: both read the same data, and each is shaped for its own kind of caller.\n\nTWO KINDS OF ANSWER, KEPT APART. A FORECAST is a numerical model interpolated to a grid cell — MeteoSwiss's own ICON-CH at 1–2 km where it reaches, and every forecast response names the model that produced it. An OBSERVATION is what an instrument in a field read ten minutes ago, from SwissMetNet, Switzerland's official automatic network. They disagree, they are supposed to disagree, and nothing here merges them.\n\nEvery timestamp is RFC 3339. Forecast times carry the Swiss offset in effect for that instant; measurement times are UTC, which is how MeteoSwiss states them. Every `date` PARAMETER carries no offset and is read as Europe/Zurich local time.\n\nForecast data by Open-Meteo.com (CC BY 4.0). Measurements and the place register: MeteoSwiss and swisstopo (CC BY)."},"servers":[{"url":"https://weather.mcp.cedricm.dev"}],"paths":{"/api/v1/places":{"get":{"operationId":"listPlaces","summary":"Search Swiss places by name, or find stations near a position","description":"Give `q` to search the federal gazetteer — communes, summits, passes, mountain huts and building addresses. Give `lat` and `lon` together to get the SwissMetNet stations nearest a position, nearest first.\n\nTHE TWO MODES ANSWER TWO DIFFERENT REGISTERS and the `kind` field says which. A reverse lookup against the gazetteer is deliberately not offered: the geoportal's `identify` endpoint needs a real map extent for its tolerance to mean anything, and asked for a point in Zermatt without one it returns communes on the other side of the country. Stations have exact published positions and cover the whole country.\n\nNOT PAGED: neither register offers a cursor, so `next_cursor` is never present.\n\nSwitzerland only. A name outside it is a 404 rather than a silent fallback to a worldwide index, because a `Bern` that quietly became Bern, Indiana is the failure this API exists to make impossible.","tags":["places"],"parameters":[{"name":"q","in":"query","required":false,"description":"Name to search for. Partial names match.","schema":{"type":"string"}},{"name":"lat","in":"query","required":false,"description":"WGS84 latitude. Requires `lon`.","schema":{"type":"number","minimum":-90,"maximum":90}},{"name":"lon","in":"query","required":false,"description":"WGS84 longitude. Requires `lat`.","schema":{"type":"number","minimum":-180,"maximum":180}},{"name":"limit","in":"query","required":false,"description":"Places per response, at most 10.","schema":{"type":"integer","minimum":1,"maximum":10,"default":6}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","title":"Place","properties":{"name":{"type":"string"},"lat":{"type":"number"},"lon":{"type":"number"},"canton":{"type":["string","null"],"description":"Two letters, where the register names one."},"elevation_metres":{"type":["number","null"],"description":"Known for a station. Null for a gazetteer result: the gazetteer holds no height."},"kind":{"type":"string","enum":["place","station"],"description":"`place` came from the swisstopo gazetteer, which is what a name search returns. `station` is a SwissMetNet instrument, which is what a coordinate search returns."},"feature_type":{"type":["string","null"]},"context":{"type":["string","null"]},"station":{"type":["string","null"],"description":"Three-letter SwissMetNet code. Pass it to /stations/{station}."},"distance_metres":{"type":["integer","null"]}},"required":["name","lat","lon","canton","elevation_metres","kind","feature_type","context","station","distance_metres"]}},"next_cursor":{"type":"string","description":"Pass as `cursor` for the next page. Absent when this is the last page."}},"required":["items"]}}}},"400":{"description":"The request was malformed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No token, or a token this server did not accept. The WWW-Authenticate header names the issuer to get one from.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The token is valid but does not carry the weather:places.read scope. The WWW-Authenticate header names it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"oauth2":["weather:places.read"]}]}},"/api/v1/forecast":{"get":{"operationId":"getForecastAt","summary":"Forecast at a coordinate","description":"Current conditions and a day-by-day outlook for an arbitrary position.\n\nBEHIND THE `places` SCOPE RATHER THAN THE `weather` ONE, and that is not a filing mistake. The caller hands over a coordinate, and the coordinate is the power being exercised whatever it asks to have done with it. Filing this under the weather scope because a forecast comes back would let any client reach the geographic surface by asking for the weather at a point.\n\nFor a named place use GET /api/v1/places, and for a station use GET /api/v1/stations/{station}/forecast — both of which are behind the weather scope, because neither takes a position.\n\n`model` names what produced the answer. Inside the MeteoSwiss ICON-CH domain that is a 1–2 km grid; outside it, a global model at 11–25 km whose single cell can span a summit and the valley under it. Nothing in the upstream response distinguishes the two, so this server probes for it.","tags":["forecast"],"parameters":[{"name":"lat","in":"query","required":true,"description":"WGS84 latitude.","schema":{"type":"number","minimum":-90,"maximum":90}},{"name":"lon","in":"query","required":true,"description":"WGS84 longitude.","schema":{"type":"number","minimum":-180,"maximum":180}},{"name":"days","in":"query","required":false,"description":"Days to forecast, at most 7. Beyond five days the MeteoSwiss model has run out and a coarser one is blended in behind it, so `model` describes the near term.","schema":{"type":"integer","minimum":1,"maximum":7,"default":3}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","title":"Forecast","properties":{"subject":{"type":"object","title":"Subject","properties":{"name":{"type":"string","description":"The MATCHED name, not the query. Check it."},"lat":{"type":"number"},"lon":{"type":"number"},"canton":{"type":["string","null"]},"resolved_from":{"type":"string","enum":["name","coordinate","station"]},"station":{"type":["string","null"]}},"required":["name","lat","lon","canton","resolved_from","station"]},"model":{"type":"object","title":"Model","properties":{"id":{"type":"string","enum":["icon-ch","global"]},"name":{"type":"string"},"resolution":{"type":"string"},"note":{"type":"string"}},"required":["id","name","resolution","note"]},"elevation_metres":{"type":["number","null"],"description":"The elevation the forecast is FOR, from the model's own terrain. In the Alps a grid cell can sit well below the summit that was named."},"timezone":{"type":"string"},"current":{"type":"object","title":"CurrentConditions","properties":{"time":{"type":["string","null"],"format":"date-time"},"temperature":{"type":["number","null"]},"apparent_temperature":{"type":["number","null"]},"relative_humidity":{"type":["number","null"]},"precipitation":{"type":["number","null"],"description":"Millimetres in the last hour."},"weather_code":{"type":["integer","null"],"description":"WMO 4677, as Open-Meteo narrows it."},"cloud_cover":{"type":["number","null"]},"wind_speed":{"type":["number","null"],"description":"km/h."},"wind_direction":{"type":["number","null"],"description":"Degrees, the direction the wind comes FROM."},"wind_gusts":{"type":["number","null"]},"pressure_sea_level":{"type":["number","null"]},"is_day":{"type":["boolean","null"]}},"required":["time","temperature","weather_code"]},"daily":{"type":"array","items":{"type":"object","title":"DailyForecast","properties":{"date":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"weather_code":{"type":["integer","null"]},"temperature_max":{"type":["number","null"]},"temperature_min":{"type":["number","null"]},"apparent_temperature_max":{"type":["number","null"]},"apparent_temperature_min":{"type":["number","null"]},"sunrise":{"type":["string","null"],"format":"date-time"},"sunset":{"type":["string","null"],"format":"date-time"},"sunshine_seconds":{"type":["number","null"],"description":"Seconds, not hours."},"precipitation_mm":{"type":["number","null"]},"precipitation_probability_max":{"type":["number","null"]},"snowfall_cm":{"type":["number","null"],"description":"Centimetres of snow, not millimetres of water."},"wind_speed_max":{"type":["number","null"]},"wind_gusts_max":{"type":["number","null"]},"wind_direction_dominant":{"type":["number","null"]},"uv_index_max":{"type":["number","null"]}},"required":["date","weather_code","temperature_max","temperature_min"]}}},"required":["subject","model","elevation_metres","timezone","current","daily"]}}}},"400":{"description":"The request was malformed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No token, or a token this server did not accept. The WWW-Authenticate header names the issuer to get one from.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The token is valid but does not carry the weather:places.read scope. The WWW-Authenticate header names it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"oauth2":["weather:places.read"]}]}},"/api/v1/stations":{"get":{"operationId":"listStations","summary":"The SwissMetNet station register","description":"MeteoSwiss's automatic monitoring network: 159 stations from the Ticino lowlands to the Jungfraujoch at 3 580 m, each with its exact position and altitude. `q` matches a code, a name, or a canton.\n\nNOT PAGED: the register arrives as one document and is filtered here, so `limit` is a cut rather than a page.","tags":["stations"],"parameters":[{"name":"q","in":"query","required":false,"description":"Substring of the code or name, or a two-letter canton.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Stations per response. The whole register is 159 rows.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","title":"Station","properties":{"station":{"type":"string","description":"Three-letter code. The id used throughout this API."},"name":{"type":"string"},"canton":{"type":["string","null"]},"lat":{"type":"number"},"lon":{"type":"number"},"elevation_metres":{"type":["number","null"]},"wigos_id":{"type":["string","null"]}},"required":["station","name","canton","lat","lon","elevation_metres","wigos_id"]}},"next_cursor":{"type":"string","description":"Pass as `cursor` for the next page. Absent when this is the last page."}},"required":["items"]}}}},"400":{"description":"The request was malformed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No token, or a token this server did not accept. The WWW-Authenticate header names the issuer to get one from.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The token is valid but does not carry the weather:weather.read scope. The WWW-Authenticate header names it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"oauth2":["weather:weather.read"]}]}},"/api/v1/stations/{station}":{"get":{"operationId":"getStation","summary":"One station by code","description":"The station's name, canton, position, altitude and WIGOS identifier.","tags":["stations"],"parameters":[{"name":"station","in":"path","required":true,"description":"A SwissMetNet station code, three letters, as returned in the `station` field of GET /api/v1/stations. Case-insensitive. A name is refused: the register is the identifier.","schema":{"type":"string","pattern":"^[A-Za-z]{3}$"}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","title":"Station","properties":{"station":{"type":"string","description":"Three-letter code. The id used throughout this API."},"name":{"type":"string"},"canton":{"type":["string","null"]},"lat":{"type":"number"},"lon":{"type":"number"},"elevation_metres":{"type":["number","null"]},"wigos_id":{"type":["string","null"]}},"required":["station","name","canton","lat","lon","elevation_metres","wigos_id"]}}}},"400":{"description":"The request was malformed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No token, or a token this server did not accept. The WWW-Authenticate header names the issuer to get one from.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The token is valid but does not carry the weather:weather.read scope. The WWW-Authenticate header names it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"oauth2":["weather:weather.read"]}]}},"/api/v1/stations/{station}/observations":{"get":{"operationId":"getStationObservations","summary":"What this station is measuring right now","description":"The station's latest ten-minute reading. MEASURED, NOT MODELLED: these are instruments in a field, and they are what a forecast should be checked against.\n\nEach measurement carries its own unit and MeteoSwiss's own definition, INCLUDING THE INTEGRATION PERIOD, which is part of the meaning: `precipitation` is a total over the last ten minutes, not an hourly rate, and `sunshine_duration` is minutes out of the last ten, so its maximum is 10.\n\nA parameter a station does not carry is ABSENT from `measurements` rather than present and null. `measured_at` is UTC, which is how the source file states it, and `measured_minutes_ago` is there because a station can be offline and an empty `measurements` array is not the same as calm weather.","tags":["observations"],"parameters":[{"name":"station","in":"path","required":true,"description":"A SwissMetNet station code, three letters, as returned in the `station` field of GET /api/v1/stations. Case-insensitive. A name is refused: the register is the identifier.","schema":{"type":"string","pattern":"^[A-Za-z]{3}$"}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","properties":{"subject":{"type":"object","title":"Subject","properties":{"name":{"type":"string","description":"The MATCHED name, not the query. Check it."},"lat":{"type":"number"},"lon":{"type":"number"},"canton":{"type":["string","null"]},"resolved_from":{"type":"string","enum":["name","coordinate","station"]},"station":{"type":["string","null"]}},"required":["name","lat","lon","canton","resolved_from","station"]},"items":{"type":"array","items":{"type":"object","title":"Observation","properties":{"station":{"type":"object","title":"Station","properties":{"station":{"type":"string","description":"Three-letter code. The id used throughout this API."},"name":{"type":"string"},"canton":{"type":["string","null"]},"lat":{"type":"number"},"lon":{"type":"number"},"elevation_metres":{"type":["number","null"]},"wigos_id":{"type":["string","null"]}},"required":["station","name","canton","lat","lon","elevation_metres","wigos_id"]},"measured_at":{"type":["string","null"],"format":"date-time"},"measured_minutes_ago":{"type":["integer","null"]},"distance_metres":{"type":["integer","null"],"description":"Null when the station was named directly. Zero would be a measurement of nothing."},"measurements":{"type":"array","items":{"type":"object","title":"Measurement","properties":{"name":{"type":"string","enum":["temperature","dew_point","relative_humidity","precipitation","sunshine_duration","global_radiation","wind_speed","wind_gust","wind_direction","pressure_station","pressure_sea_level","pressure_qnh","geopotential_850hpa"]},"value":{"type":"number"},"unit":{"type":"string"},"description":{"type":"string","description":"MeteoSwiss's own definition, including the integration period."}},"required":["name","value","unit","description"]}}},"required":["station","measured_at","measured_minutes_ago","distance_metres","measurements"]}}},"required":["subject","items"]}}}},"400":{"description":"The request was malformed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No token, or a token this server did not accept. The WWW-Authenticate header names the issuer to get one from.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The token is valid but does not carry the weather:weather.read scope. The WWW-Authenticate header names it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"oauth2":["weather:weather.read"]}]}},"/api/v1/stations/{station}/forecast":{"get":{"operationId":"getStationForecast","summary":"Forecast at a station's position","description":"Current conditions and a day-by-day outlook for where this station stands. Pair it with the station's observations to compare what is predicted against what is being measured.\n\nBehind the `weather` scope rather than the `places` one, unlike GET /api/v1/forecast: a station code is a published identifier out of a federal register, not a position somebody is standing at. That this server looks a coordinate up from it does not make the caller's request a geographic one.","tags":["forecast"],"parameters":[{"name":"station","in":"path","required":true,"description":"A SwissMetNet station code, three letters, as returned in the `station` field of GET /api/v1/stations. Case-insensitive. A name is refused: the register is the identifier.","schema":{"type":"string","pattern":"^[A-Za-z]{3}$"}},{"name":"days","in":"query","required":false,"description":"Days to forecast, at most 7. Beyond five days the MeteoSwiss model has run out and a coarser one is blended in behind it, so `model` describes the near term.","schema":{"type":"integer","minimum":1,"maximum":7,"default":3}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","title":"Forecast","properties":{"subject":{"type":"object","title":"Subject","properties":{"name":{"type":"string","description":"The MATCHED name, not the query. Check it."},"lat":{"type":"number"},"lon":{"type":"number"},"canton":{"type":["string","null"]},"resolved_from":{"type":"string","enum":["name","coordinate","station"]},"station":{"type":["string","null"]}},"required":["name","lat","lon","canton","resolved_from","station"]},"model":{"type":"object","title":"Model","properties":{"id":{"type":"string","enum":["icon-ch","global"]},"name":{"type":"string"},"resolution":{"type":"string"},"note":{"type":"string"}},"required":["id","name","resolution","note"]},"elevation_metres":{"type":["number","null"],"description":"The elevation the forecast is FOR, from the model's own terrain. In the Alps a grid cell can sit well below the summit that was named."},"timezone":{"type":"string"},"current":{"type":"object","title":"CurrentConditions","properties":{"time":{"type":["string","null"],"format":"date-time"},"temperature":{"type":["number","null"]},"apparent_temperature":{"type":["number","null"]},"relative_humidity":{"type":["number","null"]},"precipitation":{"type":["number","null"],"description":"Millimetres in the last hour."},"weather_code":{"type":["integer","null"],"description":"WMO 4677, as Open-Meteo narrows it."},"cloud_cover":{"type":["number","null"]},"wind_speed":{"type":["number","null"],"description":"km/h."},"wind_direction":{"type":["number","null"],"description":"Degrees, the direction the wind comes FROM."},"wind_gusts":{"type":["number","null"]},"pressure_sea_level":{"type":["number","null"]},"is_day":{"type":["boolean","null"]}},"required":["time","temperature","weather_code"]},"daily":{"type":"array","items":{"type":"object","title":"DailyForecast","properties":{"date":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"weather_code":{"type":["integer","null"]},"temperature_max":{"type":["number","null"]},"temperature_min":{"type":["number","null"]},"apparent_temperature_max":{"type":["number","null"]},"apparent_temperature_min":{"type":["number","null"]},"sunrise":{"type":["string","null"],"format":"date-time"},"sunset":{"type":["string","null"],"format":"date-time"},"sunshine_seconds":{"type":["number","null"],"description":"Seconds, not hours."},"precipitation_mm":{"type":["number","null"]},"precipitation_probability_max":{"type":["number","null"]},"snowfall_cm":{"type":["number","null"],"description":"Centimetres of snow, not millimetres of water."},"wind_speed_max":{"type":["number","null"]},"wind_gusts_max":{"type":["number","null"]},"wind_direction_dominant":{"type":["number","null"]},"uv_index_max":{"type":["number","null"]}},"required":["date","weather_code","temperature_max","temperature_min"]}}},"required":["subject","model","elevation_metres","timezone","current","daily"]}}}},"400":{"description":"The request was malformed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No token, or a token this server did not accept. The WWW-Authenticate header names the issuer to get one from.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The token is valid but does not carry the weather:weather.read scope. The WWW-Authenticate header names it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"oauth2":["weather:weather.read"]}]}},"/api/v1/stations/{station}/forecast/hourly":{"get":{"operationId":"getStationHourlyForecast","summary":"Hour-by-hour forecast at a station's position","description":"Hourly rows starting at the CURRENT HOUR, not at midnight. Open-Meteo answers whole days, so the raw series begins with hours that have already happened; this window is anchored to the same response's current time so the first row is always next rather than this morning.\n\n`freezing_level_metres` is the altitude above which precipitation falls as snow. In this country that single number decides whether a pass is open, and no other field says it.","tags":["forecast"],"parameters":[{"name":"station","in":"path","required":true,"description":"A SwissMetNet station code, three letters, as returned in the `station` field of GET /api/v1/stations. Case-insensitive. A name is refused: the register is the identifier.","schema":{"type":"string","pattern":"^[A-Za-z]{3}$"}},{"name":"date","in":"query","required":false,"description":"One whole day, \"YYYY-MM-DD\" in Swiss local time. Absent means \"from the current hour\". A date outside the forecast window is a 400 naming the window, not an empty list.","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},{"name":"hours","in":"query","required":false,"description":"Hours to return, at most 48. Ignored when `date` is given.","schema":{"type":"integer","minimum":1,"maximum":48,"default":24}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","title":"HourlyForecast","properties":{"subject":{"type":"object","title":"Subject","properties":{"name":{"type":"string","description":"The MATCHED name, not the query. Check it."},"lat":{"type":"number"},"lon":{"type":"number"},"canton":{"type":["string","null"]},"resolved_from":{"type":"string","enum":["name","coordinate","station"]},"station":{"type":["string","null"]}},"required":["name","lat","lon","canton","resolved_from","station"]},"model":{"type":"object","title":"Model","properties":{"id":{"type":"string","enum":["icon-ch","global"]},"name":{"type":"string"},"resolution":{"type":"string"},"note":{"type":"string"}},"required":["id","name","resolution","note"]},"elevation_metres":{"type":["number","null"]},"timezone":{"type":"string"},"hours":{"type":"array","items":{"type":"object","title":"HourForecast","properties":{"time":{"type":"string","format":"date-time"},"temperature":{"type":["number","null"]},"apparent_temperature":{"type":["number","null"]},"relative_humidity":{"type":["number","null"]},"precipitation_mm":{"type":["number","null"]},"precipitation_probability":{"type":["number","null"]},"snowfall_cm":{"type":["number","null"]},"weather_code":{"type":["integer","null"]},"cloud_cover":{"type":["number","null"]},"wind_speed":{"type":["number","null"]},"wind_direction":{"type":["number","null"]},"wind_gusts":{"type":["number","null"]},"freezing_level_metres":{"type":["number","null"],"description":"Altitude above which precipitation falls as snow. In the Alps this decides whether a pass is open, and no other field in this response says it."}},"required":["time","temperature","weather_code"]}}},"required":["subject","model","elevation_metres","timezone","hours"]}}}},"400":{"description":"The request was malformed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No token, or a token this server did not accept. The WWW-Authenticate header names the issuer to get one from.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The token is valid but does not carry the weather:weather.read scope. The WWW-Authenticate header names it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"oauth2":["weather:weather.read"]}]}},"/api/v1/stations/{station}/rain":{"get":{"operationId":"getStationRain","summary":"Will it rain here, and how hard","description":"A precipitation nowcast in FIFTEEN-MINUTE steps, as a decision rather than a series. Use it for \"will I get wet on the way\" — a shower that starts at 08:40 and stops at 08:55 does not exist in an hourly average, so the hourly forecast answers that question wrongly and confidently.\n\n`peak_intensity` is the field that answers whether a jacket is enough, against the standard rain-rate bands: 3 mm spread over three hours and 3 mm in one quarter of an hour are the same `total_mm` and not the same journey.\n\n`peak_probability` CAN BE HIGH WHILE `total_mm` IS ZERO, and that is not a contradiction: the amount is an ensemble mean and the probability is the share of members that saw anything, so 0.0 mm at 55 % means half the runs had a shower and the average washed it out.\n\n`steps` carries ONLY the wet steps; a dry window is an empty array. Check `step_minutes`: it is 60 where the fifteen-minute series does not reach, and an hourly step hides short showers by construction.","tags":["forecast"],"parameters":[{"name":"station","in":"path","required":true,"description":"A SwissMetNet station code, three letters, as returned in the `station` field of GET /api/v1/stations. Case-insensitive. A name is refused: the register is the identifier.","schema":{"type":"string","pattern":"^[A-Za-z]{3}$"}},{"name":"minutes","in":"query","required":false,"description":"How long the window runs, in minutes. At most 360; for longer use the hourly forecast, which does not imply quarter-hour precision about this evening.","schema":{"type":"integer","minimum":5,"maximum":360,"default":90}},{"name":"start_in","in":"query","required":false,"description":"Minutes until departure. The window starts then rather than now, so a shower that will be over before you leave is not reported as rain.","schema":{"type":"integer","minimum":0,"maximum":360,"default":0}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","title":"RainOutlook","properties":{"subject":{"type":"object","title":"Subject","properties":{"name":{"type":"string","description":"The MATCHED name, not the query. Check it."},"lat":{"type":"number"},"lon":{"type":"number"},"canton":{"type":["string","null"]},"resolved_from":{"type":"string","enum":["name","coordinate","station"]},"station":{"type":["string","null"]}},"required":["name","lat","lon","canton","resolved_from","station"]},"model":{"type":"object","title":"Model","properties":{"id":{"type":"string","enum":["icon-ch","global"]},"name":{"type":"string"},"resolution":{"type":"string"},"note":{"type":"string"}},"required":["id","name","resolution","note"]},"step_minutes":{"type":"integer","enum":[15,60],"description":"15 from the fifteen-minute series; 60 where that series does not reach, in which case a shower shorter than an hour is averaged across it and may not appear at all."},"window_start":{"type":["string","null"],"format":"date-time"},"window_end":{"type":["string","null"],"format":"date-time"},"total_mm":{"type":"number"},"peak_intensity":{"type":"string","enum":["dry","light","moderate","heavy"],"description":"The worst single step, against the standard rain-rate bands (light under 2.5 mm/h, moderate to 7.6, heavy above). This is the field that answers whether a jacket is enough; `total_mm` does not, because the same total spread over three hours is a different journey."},"peak_probability":{"type":["number","null"],"description":"Highest share of ensemble members seeing precipitation. CAN BE HIGH WHILE `total_mm` IS ZERO: the amount is an ensemble mean, so 0.0 mm at 55 % means half the runs had a shower."},"starts_at":{"type":["string","null"],"format":"date-time"},"ends_at":{"type":["string","null"],"format":"date-time"},"steps":{"type":"array","description":"Only the WET steps. A dry window carries an empty array, not a run of zeroes.","items":{"type":"object","properties":{"time":{"type":["string","null"],"format":"date-time"},"mm":{"type":["number","null"],"description":"Millimetres in this step, not per hour."},"probability":{"type":["number","null"]},"snowfall_cm":{"type":["number","null"]},"weather_code":{"type":["integer","null"]},"intensity":{"type":"string","enum":["light","moderate","heavy"]}},"required":["time","mm","intensity"]}}},"required":["subject","model","step_minutes","total_mm","peak_intensity","peak_probability","starts_at","ends_at","steps"]}}}},"400":{"description":"The request was malformed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No token, or a token this server did not accept. The WWW-Authenticate header names the issuer to get one from.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The token is valid but does not carry the weather:weather.read scope. The WWW-Authenticate header names it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"oauth2":["weather:weather.read"]}]}},"/api/v1/observations":{"get":{"operationId":"listObservations","summary":"Measurements from the stations nearest a named place","description":"The latest readings from the SwissMetNet stations nearest a place, nearest first.\n\nMORE THAN ONE IS RETURNED BY DEFAULT, and in this country that matters: the station nearest Zermatt is 1 600 m further up a mountain, and its temperature is a fact about a ridge. The altitudes in the response are what let a caller notice.\n\nTakes a NAME and not a coordinate, which is why it sits behind the weather scope. For a position, GET /api/v1/places answers the nearest stations and their codes.","tags":["observations"],"parameters":[{"name":"place","in":"query","required":true,"description":"A Swiss place name, or a three-letter station code.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Stations per response, at most 10.","schema":{"type":"integer","minimum":1,"maximum":10,"default":3}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","properties":{"subject":{"type":"object","title":"Subject","properties":{"name":{"type":"string","description":"The MATCHED name, not the query. Check it."},"lat":{"type":"number"},"lon":{"type":"number"},"canton":{"type":["string","null"]},"resolved_from":{"type":"string","enum":["name","coordinate","station"]},"station":{"type":["string","null"]}},"required":["name","lat","lon","canton","resolved_from","station"]},"items":{"type":"array","items":{"type":"object","title":"Observation","properties":{"station":{"type":"object","title":"Station","properties":{"station":{"type":"string","description":"Three-letter code. The id used throughout this API."},"name":{"type":"string"},"canton":{"type":["string","null"]},"lat":{"type":"number"},"lon":{"type":"number"},"elevation_metres":{"type":["number","null"]},"wigos_id":{"type":["string","null"]}},"required":["station","name","canton","lat","lon","elevation_metres","wigos_id"]},"measured_at":{"type":["string","null"],"format":"date-time"},"measured_minutes_ago":{"type":["integer","null"]},"distance_metres":{"type":["integer","null"],"description":"Null when the station was named directly. Zero would be a measurement of nothing."},"measurements":{"type":"array","items":{"type":"object","title":"Measurement","properties":{"name":{"type":"string","enum":["temperature","dew_point","relative_humidity","precipitation","sunshine_duration","global_radiation","wind_speed","wind_gust","wind_direction","pressure_station","pressure_sea_level","pressure_qnh","geopotential_850hpa"]},"value":{"type":"number"},"unit":{"type":"string"},"description":{"type":"string","description":"MeteoSwiss's own definition, including the integration period."}},"required":["name","value","unit","description"]}}},"required":["station","measured_at","measured_minutes_ago","distance_metres","measurements"]}}},"required":["subject","items"]}}}},"400":{"description":"The request was malformed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No token, or a token this server did not accept. The WWW-Authenticate header names the issuer to get one from.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The token is valid but does not carry the weather:weather.read scope. The WWW-Authenticate header names it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"oauth2":["weather:weather.read"]}]}}},"components":{"schemas":{"Problem":{"type":"object","title":"Problem","description":"RFC 7807. Served as application/problem+json.","properties":{"type":{"type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"required":["title","status"]}},"securitySchemes":{"oauth2":{"type":"oauth2","description":"Tokens come from the issuer below. A client that has none can discover the issuer from /.well-known/oauth-protected-resource, which is what lets a tool connect having been told nothing but this URL.","flows":{"authorizationCode":{"authorizationUrl":"https://auth.cedricm.dev/authorize","tokenUrl":"https://auth.cedricm.dev/token","refreshUrl":"https://auth.cedricm.dev/token","scopes":{"weather:weather.read":"Read the forecast for a named place and the latest measurements from Switzerland's official automatic weather stations.","weather:places.read":"Search Swiss places by name, and find what is nearest a coordinate. Searching by coordinate means telling this server where somebody is, which reading a forecast for a named place does not."}}}}}}}