Rajapinta (API) ja webhookit
Avoimet osoitteet toimivat kaikille. Yritys- ja organisaatiotilauksiin kuuluu avaimella käytettävä rajapinta ilman välimuistiviivettä sekä webhookit Slackiin, Teamsiin, Discordiin tai omaan järjestelmään.
Tunnistautuminen ja rajat
Luo avain tilisi Organisaatio-osiossa (enintään 3 avainta). Avain näytetään vain kerran, ja me tallennamme siitä vain tiivisteen. Lähetä se otsakkeessa Authorization: Bearer fwsi_live_…. Rajat avainta kohden: 60 pyyntöä minuutissa ja 10 000 vuorokaudessa; rajan ylittyessä vastaus on 429 ja Retry-After kertoo, milloin voi jatkaa.
curl -H "Authorization: Bearer fwsi_live_…" https://sotatila.fi/api/v1/latest
Osoitteet
| Osoite | Käyttö | Parametrit | Sisältö |
|---|---|---|---|
GET /api/v1/alerts | avain | since limit | Alerts raised by verified readings, newest first. |
GET /api/v1/briefings/date | avain | lang | The daily briefing of a date (Europe/Helsinki), as HTML and plain text. |
GET /api/v1/events | avoin | pillar type from | Events |
GET /api/v1/events/event_id | avain | One event with its revisions, Finnish title and why it matters. | |
GET /api/v1/forecast/latest | avain | The no-news projection for the next 7 days (SPEC §7.7 part 1): where the index drifts if nothing new happens. | |
GET /api/v1/latest | avain | The newest reading straight from the source: interim readings included, full pillar detail, no CDN delay. | |
GET /api/v1/readings | avoin | from to vintage | Readings |
GET /api/v1/status | avoin | Status |
Webhookit
Lisää enintään 5 osoitetta (vain julkiset https-osoitteet). Tapahtumat: reading.published, alert.* ja briefing.published. Slack, Teams ja Discord saavat valmiin viestin, generic alla olevan JSONin. Epäonnistunut toimitus yritetään uudelleen 1, 5, 15 ja 60 minuutin kuluttua; 24 tuntia vastaamatta ollut osoite poistetaan käytöstä ja omistajalle lähetetään sähköposti.
Esimerkki: generic-tapahtuma
{
"id": "evt_a123",
"type": "alert.ewl_up",
"created_at": "2026-12-01T06:05:00+00:00",
"reading": {
"date": "2026-12-01",
"wsi": 41.2,
"band": "High",
"ewl": 2,
"ewl_name": "Elevated",
"delta_24h": 9.1,
"top_signals": [
"…"
]
},
"alert": {
"code": "ewl_up",
"text_fi": "…",
"text_en": "…"
},
"links": {
"briefing": "https://sotatila.fi/fi/tili/katsaukset/123/",
"dashboard": "https://sotatila.fi/fi/"
}
}
Allekirjoituksen tarkistus
Jokaisessa pyynnössä on otsake X-FWSI-Signature: t=<aika>,v1=<allekirjoitus>, jossa allekirjoitus on HMAC-SHA256 webhookin salaisella avaimella merkkijonosta <aika>.<pyynnön runko>. Tarkista se ennen kuin luotat sisältöön, ja hylkää vanhat aikaleimat. Esimerkkikoodi (Python):
"""Verify an FWSI webhook (sample code for customers; also run by the test suite).
FWSI signs every request: X-FWSI-Signature: t=<unix time>,v1=<hex HMAC-SHA256(secret, "<t>.<raw body>")>
Use the raw request body exactly as received, and reject old timestamps to stop replays.
"""
import hashlib
import hmac
import time
def verify(secret: str, signature_header: str, raw_body: bytes, tolerance_s: int = 300) -> bool:
try:
parts = dict(item.split("=", 1) for item in signature_header.split(","))
t, given = int(parts["t"]), parts["v1"]
except (ValueError, KeyError):
return False
if abs(time.time() - t) > tolerance_s:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, given)
# Flask example:
# @app.post("/fwsi")
# def fwsi_hook():
# if not verify(SECRET, request.headers["X-FWSI-Signature"], request.get_data()):
# abort(401)
# event = request.get_json()
# ...
Käyttöehdot
Rajapinnan tietoja saa käyttää organisaation omaan käyttöön. Edelleenjakelu vaatii Organisaatiot-sopimuksen. Avoin data (/data/) on CC BY 4.0 -lisenssillä.