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_v9 · refreshed 2026-06-14 · 53 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(sigmoid(0.6*logit(shutdown_risk_v4_prob) + 0.40*logit(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 53 countries sorted by 7-day shutdown probability.
- 1MMelevated45.4%
- Country risk
- 43.8%
- OONI signal
- 74.7%
- As of
- 2026-06-13
- 2RUelevated28.1%
- Country risk
- 18.5%
- OONI signal
- 80.0%
- As of
- 2026-06-13
- 3PKelevated23.5%
- Country risk
- 18.5%
- OONI signal
- 73.4%
- As of
- 2026-06-13
- 4INlow6.2%
- Country risk
- 43.8%
- OONI signal
- 3.0%
- As of
- 2026-06-13
- 5IQlow6.2%
- Country risk
- 5.7%
- OONI signal
- 78.8%
- As of
- 2026-06-13
- 6JOlow6.2%
- Country risk
- 5.3%
- OONI signal
- 63.0%
- As of
- 2026-06-13
- 7TZlow6.2%
- Country risk
- 7.0%
- OONI signal
- 69.0%
- As of
- 2026-06-13
- 8IRlow2.9%
- Country risk
- 5.7%
- OONI signal
- 22.2%
- As of
- 2026-06-13
- 9SDlow2.9%
- Country risk
- 1.9%
- OONI signal
- 60.2%
- As of
- 2026-06-10
- 10BYlow2.8%
- Country risk
- 1.0%
- OONI signal
- 75.4%
- As of
- 2026-06-13
- 11ETlow1.8%
- Country risk
- 1.0%
- OONI signal
- 67.4%
- As of
- 2026-06-13
- 12SYlow1.8%
- Country risk
- 1.9%
- OONI signal
- 49.1%
- As of
- 2026-06-13
- 13KGlow1.5%
- Country risk
- 1.0%
- OONI signal
- 62.8%
- As of
- 2026-06-13
- 14NGlow1.5%
- Country risk
- 1.0%
- OONI signal
- 63.8%
- As of
- 2026-06-13
- 15OMlow1.5%
- Country risk
- 1.0%
- OONI signal
- 54.6%
- As of
- 2026-06-13
- 16TMlow1.1%
- Country risk
- 1.0%
- OONI signal
- 46.8%
- As of
- 2025-09-26
- 17VElow1.1%
- Country risk
- 1.9%
- OONI signal
- 23.8%
- As of
- 2026-06-13
- 18AElow0.4%
- Country risk
- 0.1%
- OONI signal
- 65.0%
- As of
- 2026-06-13
- 19AZlow0.4%
- Country risk
- 0.1%
- OONI signal
- 78.4%
- As of
- 2026-06-13
- 20BFlow0.4%
- Country risk
- 0.1%
- OONI signal
- 80.3%
- As of
- 2026-06-13
- 21BHlow0.4%
- Country risk
- 0.1%
- OONI signal
- 69.5%
- As of
- 2026-06-11
- 22CNlow0.4%
- Country risk
- 1.0%
- OONI signal
- 1.9%
- As of
- 2026-06-13
- 23CUlow0.4%
- Country risk
- 0.1%
- OONI signal
- 72.4%
- As of
- 2026-06-13
- 24EGlow0.4%
- Country risk
- 0.1%
- OONI signal
- 87.0%
- As of
- 2026-06-13
- 25ESlow0.4%
- Country risk
- 0.1%
- OONI signal
- 89.4%
- As of
- 2026-06-13
- 26FIlow0.4%
- Country risk
- 0.1%
- OONI signal
- 40.9%
- As of
- 2026-06-13
- 27GNlow0.4%
- Country risk
- 1.0%
- OONI signal
- 26.3%
- As of
- 2026-06-13
- 28KElow0.4%
- Country risk
- 1.9%
- OONI signal
- 5.2%
- As of
- 2026-06-13
- 29KZlow0.4%
- Country risk
- 0.1%
- OONI signal
- 72.5%
- As of
- 2026-06-13
- 30LKlow0.4%
- Country risk
- 0.1%
- OONI signal
- 67.7%
- As of
- 2026-06-11
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
8 predictions archived, 8 crossings detected to date- PKelevatedfrom watch → elevatedp =
23.8%on 2026-06-08view JSON → - TZwatchfrom low → watchp =
10.9%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 → - RUelevatedfrom watch → elevatedp =
22.8%on 2026-05-15view 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
- ·FORWARD-VALIDATED (the real product): in a strict past->future temporal holdout the CROSS-COUNTRY ranking holds — full-panel AUC ~0.87, country-ranking AUC ~0.90, 7.8x PR-lift over base rate. The model reliably tells you WHICH countries are heading into danger, forward in time. BUT the within-country median AUC is CROSS-SECTIONAL (full panel, in-sample isotonic) — it measures day-ranking when the model has seen the whole period, NOT forward skill. In a strict past->future temporal holdout, within-country timing AUC is ~0.38 (below chance), for BOTH v5 and v9, driven by non-stationarity (shutdowns cluster in time). USE THIS MODEL FOR *WHICH* COUNTRY IS AT RISK (cross-country full-panel AUC 0.90, which IS forward-meaningful), NOT as a within-country *WHEN* oracle.
- ·v9 replaces v5's multiplicative product with a logit-additive blend (alpha=0.6 on structural risk). The product collapsed to ~0 when honest_forecast was near-zero for a country, annihilating a good structural signal; the logit blend cannot zero out either factor.
- ·Within-country median AUC 0.735 vs v5 0.729. Improvement confirmed by leave-one-country-out alpha selection (all 23 folds chose alpha=0.60; held-out median +0.0095, mean +0.0131, 18/23 countries up).
- ·Still country-level; sub-national shutdowns (~half of KeepItOn events) remain harder. Low-N countries are sample-size limited, not model-limited.
- ·Same two honestly-validated base models as v5 (v4 structural + honest_forecast_v1). No new external features (unlike the gated v7 CF-Radar and v8 GDELT attempts) — purely a better combiner.
For the related but distinct OONI-anomaly forecast (different question), see /v1/forecast/honest/{country}.