Quick start
Hi! This is the API behind the Magdeburg weather site, and you are welcome to use
it on yours. Everything is a plain GET that returns JSON. Want
German text? Add ?lang=de to any endpoint.
curl https://weather.jroering.com/api/v1/fsi
{
"score": 8.5,
"band": "Excellent",
"band_key": "excellent",
"color": "#40ad3e",
"advice": "Perfect suiting conditions. Touch grass and enjoy the festival!",
"wetbulb_c": 16.8,
"effective_wetbulb_c": 16.8,
"subscores": [ … ],
"meta": { "station_id": "10361", "language": "en", … }
}
This page is the short version. Full schemas live at /docs, and the raw spec is at /openapi.json.
Two rules
-
Build against
/api/v1.: it will likely stay stable, and it is the only part of the site that is published. - Use responsefully: Server no likey big useages. Pwease be kind to the sewvew. uwu
Endpoints
| Endpoint | Returns | Size |
|---|---|---|
/api/v1/fsi |
The index right now: score, band, colour, advice, four sub-scores | ~1 kB |
/api/v1/current |
Latest station reading: temperature, wind, pressure, cloud | ~0.7 kB |
/api/v1/forecast |
Weather and index, hour by hour. ?hours=1…120, default 24 |
~7 kB |
/api/v1/daily |
Per day: min, max and average index, best and worst hour, totals | ~3 kB |
/api/v1/warnings |
Official DWD warnings and advance notices in force | ~2 kB |
/api/v1/scale |
The band thresholds and their colours | ~0.6 kB |
/api/v1/overview |
All of the above in one call, same shapes | ~15 kB |
/api/health |
Liveness, plus how old each cached source is | ~0.3 kB |
Every response carries meta
So you can always tell where the numbers came from:
station_id,location,latitude/longitude,timezone: what was measured, and wheregenerated_at: ISO 8601 UTCattribution: the DWD credit you have to reproduce-
degraded: sources that were unreachable for this response. We answer with what we have instead of failing, so check this array before you treat a response as complete.
The Fursuiting Index
The FSI is a 0 to 10 score for how safe and how pleasant it is to be in a
fullsuit outdoors. 0 is grim, 10 is perfect. It assumes no cooling vest and
no fans, so treat it as a hint rather than a verdict. Four weighted parts
feed it, and each one comes back with its own score,
weight and a plain reason:
key | Weight | What it looks at |
|---|---|---|
thermal_humidity | 50 % | Wet-bulb temperature plus sun load. The big one. |
precipitation | 30 % | Rain rate blended with the chance of rain, plus wet ground |
wind | 12 % | A light breeze is ideal. Still air and gales both hurt. |
stickiness | 8 % | Dew point. Above 15 °C the air feels clammy. |
It is not just a weighted mean. The score is the lower of
that mean and the thermal_humidity and
precipitation sub-scores, so neither dangerous heat nor rain
can be averaged away: without that, a perfect "it is not raining" would drag
a genuinely unsafe hour up into the middle of the scale. When one of the two
bites, the score is simply equal to that sub-score.
Switch on band_key, not band
band is translated text and changes with ?lang=.
band_key stays put: excellent, good,
fair, poor, bad. Colours come from
/api/v1/scale, defined once on the server so your charts cannot
drift out of step with ours. An hour counts as suitable at or above
suitable_from (5.0).
Warnings
Warnings do not change the index. They sit next to it and
you decide what to do with them. A warning covers a whole region for hours
at a time, so folding it into the score flattened the hour by hour detail
the forecast exists to show. The hazards that matter for suiting are already
measured per hour in the sub-scores. If you want them combined, do it
yourself from /api/v1/warnings.
advance: true marks a Vorabinformation. That is DWD
flagging possible severe weather before it is certain enough to warn on, so
please do not show it as a warning in force. This site draws it with red
diagonal hatching.
headline, description and instruction
are DWD's official wording and are always German, whatever
lang you ask for. Passing them through untouched is deliberate:
translating official safety text is not ours to do. Only label
is translated. Use kind and severity for logic.
Rate limit & caching
100 requests per minute per client, on a sliding window. Every response tells you where you stand:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 97
Go over and you get a 429 with a Retry-After header
and retry_after_seconds in the body. The pages themselves are
not limited, so a display board can never lock itself out of its own HTML.
Please cache
The limit is a politeness ceiling, not a challenge. Polling faster buys you
nothing, because the upstream data does not move that fast. Every response
carries an honest Cache-Control you can just obey.
| Source | DWD publishes | We cache |
|---|---|---|
| Station observations | hourly | 10 min |
| MOSMIX point forecast | every ~6 h | 30 min |
| Warnings | every few minutes | 3 min |
Once a minute is plenty. This site's own frontend refreshes every five minutes.
Errors
| Status | Meaning |
|---|---|
422 | A parameter failed validation, e.g. lang=fr, wind_unit=mps, or hours outside 1…120 |
429 | Rate limited. The body has retry_after_seconds |
503 | No usable DWD data at all right now |
One source being down is not an error. The JSON endpoints still answer with
everything else and name the casualty in meta.degraded. A failed
refresh also falls back to the last good value, so you may get slightly stale
data rather than none. That is why generated_at and
observed_at are worth reading.
Attribution & fair use
All weather data comes from Deutscher Wetterdienst (DWD) and
is used under
GeoNutzV.
If you republish it, credit DWD. The exact string is in
meta.attribution of every response.
This is a local weather site for Magdeburg. It is not an official DWD service, and the index is advice, not a safety certification: heat exhaustion is real, and no number replaces your own judgement or a headless break. Please do not lean on it for decisions where being wrong matters.
If you build something with it, that is genuinely welcome. Just cache, and
set a User-Agent so a runaway script can be told apart from a
person.