Shutdown Risk
Per-country 7-day internet shutdown risk forecast. Trained and validated on real, journalist-verified shutdowns from the Access Now #KeepItOn coalition — not on a proxy this project invented.
Model shutdown_risk_v5 · refreshed 2026-05-24 · 60 countries scored · 3 elevated · 0 on watch
Honest validation
Every probability below was scored against real KeepItOn shutdowns. The system reports its own performance with every response.
Score formula: isotonic(shutdown_risk_v4_prob * honest_forecast_v1_prob). Threshold for "elevated": 0.180.
Data sources
Every number on this page traces back to a public, independent, citable dataset. None of the labels are invented by this project.
Access Now #KeepItOn STOP
Journalist- and civil-society-verified internet shutdowns worldwide. 287 verified onsets in our prediction window (2022–2026), spanning 44 countries. The model trains and validates on these — not on a proxy this project invented.
KeepItOn data dashboard →OONI aggregation API
Measurements taken by volunteer OONI Probe users inside the countries being measured. 85,543 country-days across 60 countries, 2022–2026. Each day has real measurement_count denominators — not a synthetic proxy.
OONI public data →Pipeline: scripts/pull-ooni-aggregation.py + KeepItOn STOP CSV are pulled daily, the ensemble retrains, and the leaderboard above refreshes. Source code: /scripts in the repo.
Today’s risk ranking
Top 30 of 60 countries sorted by 7-day shutdown probability.
- 1MMelevated56.6%
- Country risk
- 45.8%
- OONI signal
- 83.0%
- As of
- 2026-05-16
- 2PKelevated22.6%
- Country risk
- 19.6%
- OONI signal
- 72.3%
- As of
- 2026-05-16
- 3RUelevated22.6%
- Country risk
- 19.6%
- OONI signal
- 81.0%
- As of
- 2026-05-16
- 4IQlow5.9%
- Country risk
- 5.8%
- OONI signal
- 67.6%
- As of
- 2026-05-16
- 5JOlow5.9%
- Country risk
- 5.6%
- OONI signal
- 81.6%
- As of
- 2026-05-16
- 6TZlow5.9%
- Country risk
- 7.4%
- OONI signal
- 74.5%
- As of
- 2026-05-16
- 7IRlow3.1%
- Country risk
- 5.8%
- OONI signal
- 29.9%
- As of
- 2026-05-16
- 8SYlow2.9%
- Country risk
- 2.0%
- OONI signal
- 47.9%
- As of
- 2026-05-16
- 9BYlow2.4%
- Country risk
- 1.0%
- OONI signal
- 83.7%
- As of
- 2026-05-16
- 10KGlow2.4%
- Country risk
- 1.0%
- OONI signal
- 83.1%
- As of
- 2026-05-16
- 11ETlow2.0%
- Country risk
- 1.0%
- OONI signal
- 45.2%
- As of
- 2026-05-16
- 12INlow2.0%
- Country risk
- 45.8%
- OONI signal
- 1.1%
- As of
- 2026-05-16
- 13KElow2.0%
- Country risk
- 2.0%
- OONI signal
- 20.8%
- As of
- 2026-05-16
- 14NGlow2.0%
- Country risk
- 1.0%
- OONI signal
- 64.1%
- As of
- 2026-05-16
- 15OMlow2.0%
- Country risk
- 1.0%
- OONI signal
- 72.9%
- As of
- 2026-05-16
- 16TGlow2.0%
- Country risk
- 1.0%
- OONI signal
- 30.9%
- As of
- 2026-05-16
- 17VElow2.0%
- Country risk
- 2.0%
- OONI signal
- 15.3%
- As of
- 2026-05-16
- 18GNlow0.9%
- Country risk
- 1.0%
- OONI signal
- 19.1%
- As of
- 2026-05-16
- 19VNlow0.7%
- Country risk
- 1.0%
- OONI signal
- 12.7%
- As of
- 2026-05-16
- 20AElow0.4%
- Country risk
- 0.1%
- OONI signal
- 78.3%
- As of
- 2026-05-16
- 21AZlow0.4%
- Country risk
- 0.1%
- OONI signal
- 72.0%
- As of
- 2026-05-16
- 22BFlow0.4%
- Country risk
- 0.1%
- OONI signal
- 68.3%
- As of
- 2026-05-16
- 23BHlow0.4%
- Country risk
- 0.1%
- OONI signal
- 67.4%
- As of
- 2026-05-16
- 24CHlow0.4%
- Country risk
- 0.1%
- OONI signal
- 28.7%
- As of
- 2026-05-16
- 25CNlow0.4%
- Country risk
- 1.0%
- OONI signal
- 8.4%
- As of
- 2026-05-16
- 26CUlow0.4%
- Country risk
- 0.1%
- OONI signal
- 31.7%
- As of
- 2026-05-15
- 27EGlow0.4%
- Country risk
- 0.1%
- OONI signal
- 69.0%
- As of
- 2026-05-16
- 28ESlow0.4%
- Country risk
- 0.1%
- OONI signal
- 59.8%
- As of
- 2026-05-16
- 29FIlow0.4%
- Country risk
- 0.1%
- OONI signal
- 19.7%
- As of
- 2026-05-16
- 30IDlow0.4%
- Country risk
- 1.0%
- OONI signal
- 2.2%
- As of
- 2026-05-16
API: GET https://api.voidly.ai/v1/shutdown-risk/{country}
Get notified when a country crosses elevated
Every band-crossing (low → watch, watch → elevated) is published as an RSS event the moment it’s detected. Journalists, activists, VPN providers, and AI agents can subscribe and react in real time — no polling needed.
Latest band crossings
14 predictions archived, 14 crossings detected to date- TZwatchfrom low → watchp =
10.9%on 2026-05-15view JSON → - TZwatchfrom low → watchp =
10.9%on 2026-05-15view JSON → - PKelevatedfrom watch → elevatedp =
22.8%on 2026-05-15view JSON → - PKelevatedfrom watch → elevatedp =
22.8%on 2026-05-15view JSON → - PKwatchfrom (none) → watchp =
19.7%on 2026-05-14view JSON →
Live from /v1/shutdown-risk/alerts. Sorted newest-first. Each row links to the per-country endpoint with its full validation embedded.
/v1/shutdown-risk/feed.rss →
Standards-compliant RSS. Add to Feedly, Inoreader, NetNewsWire, or any RSS reader. One item per country crossing, with the probability, model version, and a permalink to the live per-country endpoint.
/v1/shutdown-risk/alerts →
Same alerts, machine-readable. Poll from a script, an AI agent, or pipe into your own monitoring. Each event includes band, previous band, probability, KeepItOn evidence, and a permalink.
One-click webhook subscribe — no account needed
Paste an https:// webhook URL. We send a test ping first — if it responds 2xx, we wire it up. Every band crossing for your selection is delivered as JSON via POST (same payload shape as /v1/shutdown-risk/alerts). The webhook URL is the secret; hold onto the unsubscribe link we return to revoke later.
Cooldown: 14 days per (country, band) suppresses repeat alerts. Computed daily by compute-shutdown-risk-alerts.py from the same prediction archive the accountability loop uses.
Live accountability
running_accountability tracks every archived shutdown_risk prediction (one per country per day, from archive-shutdown-risk-history.py) against the actual KeepItOn-documented shutdowns that followed in the 7 days after. early days have small sample size; validated_backtest contains the v4 trainer's static AUC-0.90 result (full-panel).
The loop has just started — precision and recall stabilise once a few weeks of outcomes have landed.
API: GET https://api.voidly.ai/v1/shutdown-risk/accountability
Integrate it
The endpoints are free and CORS-open. Copy-paste to start.
curl -s https://api.voidly.ai/v1/shutdown-risk/IN | jq .risk
# -> { "probability": 0.46, "risk_band": "elevated",
# "components": { "v5_prob": ..., "sr_prob": ..., "hf_prob": ... },
# "kio_hist_5y": 64, "kio_recent_7d": 0, "as_of_date": "..." }import requests
data = requests.get("https://api.voidly.ai/v1/shutdown-risk/leaderboard").json()
for cc, rec in sorted(
data["countries"].items(),
key=lambda kv: -kv[1]["probability"],
)[:10]:
print(f"{cc} {rec['probability']:.0%} {rec['risk_band']}")import Parser from "rss-parser";
const feed = await new Parser().parseURL(
"https://api.voidly.ai/v1/shutdown-risk/feed.rss",
);
for (const item of feed.items) {
console.log(item.pubDate, item.title); // e.g. "RU: shutdown risk ELEVATED (p=22.8%)"
}# Register once. Every band-crossing for Iran (or any country) is POSTed
# to your endpoint in real time via the worker's scheduled fanout.
# Anonymous — the webhook URL is the secret. We send a test ping first;
# if it doesn't 2xx, nothing is stored.
curl -X POST https://api.voidly.ai/v1/shutdown-risk/subscribe \
-H "Content-Type: application/json" \
-d '{
"webhook_url": "https://your-server.com/voidly-hooks",
"country_code": "IR",
"min_severity": "high",
"event_types": ["shutdown_risk_elevated", "shutdown_risk_watch"]
}'
# Response includes an unsubscribe_url you can save to revoke later.
# Payload your endpoint receives on a real crossing:
# { "event": "shutdown_risk.elevated", "country_code": "IR",
# "date": "...", "probability": 0.46, "band": "elevated",
# "from_band": "watch", "alert_id": "...",
# "report_url": "https://api.voidly.ai/v1/shutdown-risk/IR" }# Fire one synthetic shutdown_risk.elevated payload to your webhook
# right now. country_code is "XX" and test:true so downstream can
# sandbox it. Returns upstream HTTP status + latency.
curl -X POST https://api.voidly.ai/v1/shutdown-risk/test-alert \
-H "Content-Type: application/json" \
-d '{
"webhook_url": "https://your-server.com/voidly-hooks",
"band": "elevated"
}'# Every shutdown_risk webhook delivery (the test ping, the test-alert
# sample, and real band crossings) carries:
# X-Voidly-Signature: sha256=<hmac-sha256(body, WEBHOOK_SECRET)>
# X-Voidly-Event: shutdown_risk.{connected|watch|elevated}
#
# Same WEBHOOK_SECRET signs all three — verify once, accept all of them.
# Contact us to receive your subscriber-specific secret; until then,
# subscriptions ship with a per-deployment shared default.
import hmac, hashlib
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = b"your-webhook-secret" # from /v1/shutdown-risk/subscribe response
@app.post("/voidly-hooks")
def hook():
sig = request.headers.get("X-Voidly-Signature", "")
expected = "sha256=" + hmac.new(SECRET, request.data, hashlib.sha256).hexdigest()
if not hmac.compare_digest(sig, expected):
abort(401, "bad signature")
evt = request.json
print(evt["event"], evt["country_code"], evt["band"], evt["probability"])
return ("", 204)# In your cron / GitHub Action: poll the JSON event log and post new
# items to a Slack incoming webhook.
ALERTS=$(curl -s https://api.voidly.ai/v1/shutdown-risk/alerts | jq -c '.alerts[0]')
COUNTRY=$(echo "$ALERTS" | jq -r .country)
BAND=$(echo "$ALERTS" | jq -r .band)
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"⚠️ Shutdown risk \\`$BAND\\` in $COUNTRY\"}" \
$SLACK_WEBHOOK_URLHonest caveats
- ·Ensemble of two honestly-validated models. shutdown_risk_v4 provides country structural risk (the cross-country signal); honest_forecast_v1 provides OONI z-score trajectory (the within-country timing signal). The product captures WHICH and WHEN.
- ·Within-country median AUC 0.73. The model differentiates shutdown-precursor days from other days within the major shutdown-prone countries (MM, IN, RU, PK, IQ all >=0.70). For low-N countries the within-country signal is weaker — sample-size limited, not model-limited.
- ·Country structural risk dominates the cross-country signal; OONI measurement trajectory adds the within-country timing.
- ·City-level / sub-national shutdowns (~half of KeepItOn events) remain harder to predict from country-level aggregates.
- ·v1/v2/v3 lost the signal by over-fitting; v4 captured country-identity; v5 is the minimum ensemble that adds within-country timing on top of v4.
For the related but distinct OONI-anomaly forecast (different question), see /v1/forecast/honest/{country}.