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-07-08 · 53 countries scored · 2 elevated · 7 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.100.
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.
- 1MMelevated54.2%
- Country risk
- 41.6%
- OONI signal
- 84.8%
- As of
- 2026-07-07
- 2PKelevated27.6%
- Country risk
- 17.5%
- OONI signal
- 81.6%
- As of
- 2026-07-07
- 3RUwatch9.1%
- Country risk
- 17.5%
- OONI signal
- 48.1%
- As of
- 2026-07-07
- 4TZwatch9.1%
- Country risk
- 6.7%
- OONI signal
- 82.6%
- As of
- 2026-07-07
- 5INwatch6.0%
- Country risk
- 41.6%
- OONI signal
- 9.2%
- As of
- 2026-07-07
- 6IQwatch6.0%
- Country risk
- 5.5%
- OONI signal
- 70.1%
- As of
- 2026-07-07
- 7IRwatch6.0%
- Country risk
- 5.5%
- OONI signal
- 59.2%
- As of
- 2026-07-07
- 8JOwatch6.0%
- Country risk
- 5.1%
- OONI signal
- 52.5%
- As of
- 2026-07-07
- 9SDwatch6.0%
- Country risk
- 1.8%
- OONI signal
- 82.6%
- As of
- 2026-06-19
- 10OMlow5.9%
- Country risk
- 1.0%
- OONI signal
- 85.0%
- As of
- 2026-07-04
- 11SYlow2.5%
- Country risk
- 1.8%
- OONI signal
- 64.3%
- As of
- 2026-07-07
- 12KGlow2.4%
- Country risk
- 1.0%
- OONI signal
- 74.0%
- As of
- 2026-07-07
- 13VElow2.1%
- Country risk
- 1.8%
- OONI signal
- 50.1%
- As of
- 2026-07-07
- 14BYlow1.4%
- Country risk
- 1.0%
- OONI signal
- 55.1%
- As of
- 2026-07-07
- 15ETlow1.4%
- Country risk
- 1.0%
- OONI signal
- 59.0%
- As of
- 2026-07-07
- 16NGlow1.4%
- Country risk
- 1.0%
- OONI signal
- 53.8%
- As of
- 2026-07-07
- 17TMlow1.2%
- Country risk
- 1.0%
- OONI signal
- 44.8%
- As of
- 2025-09-26
- 18GNlow0.5%
- Country risk
- 1.0%
- OONI signal
- 35.6%
- As of
- 2026-07-07
- 19UGlow0.5%
- Country risk
- 0.1%
- OONI signal
- 86.6%
- As of
- 2026-07-07
- 20CUlow0.4%
- Country risk
- 0.1%
- OONI signal
- 76.3%
- As of
- 2026-07-07
- 21TRlow0.4%
- Country risk
- 5.1%
- OONI signal
- 1.6%
- As of
- 2026-07-07
- 22AElow0.3%
- Country risk
- 0.1%
- OONI signal
- 73.4%
- As of
- 2026-07-07
- 23AZlow0.3%
- Country risk
- 0.1%
- OONI signal
- 72.1%
- As of
- 2026-07-07
- 24BFlow0.3%
- Country risk
- 0.1%
- OONI signal
- 74.5%
- As of
- 2026-07-07
- 25BHlow0.3%
- Country risk
- 0.1%
- OONI signal
- 69.4%
- As of
- 2026-07-07
- 26CNlow0.3%
- Country risk
- 1.0%
- OONI signal
- 8.9%
- As of
- 2026-07-07
- 27EGlow0.3%
- Country risk
- 0.1%
- OONI signal
- 43.5%
- As of
- 2026-07-07
- 28ESlow0.3%
- Country risk
- 0.1%
- OONI signal
- 40.8%
- As of
- 2026-07-07
- 29IElow0.3%
- Country risk
- 0.1%
- OONI signal
- 48.2%
- As of
- 2026-07-07
- 30KElow0.3%
- Country risk
- 1.8%
- OONI signal
- 4.8%
- As of
- 2026-07-07
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
19 predictions archived, 19 crossings detected to date- IRwatchfrom low → watchp =
12.5%on 2026-07-05view JSON → - INelevatedfrom watch → elevatedp =
15.7%on 2026-07-05view JSON → - SYwatchfrom low → watchp =
9.6%on 2026-07-03view JSON → - RUwatchfrom low → watchp =
10.9%on 2026-07-03view JSON → - JOwatchfrom low → watchp =
6.4%on 2026-07-01view 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.715 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.
- ·WHAT 'low' MEANS HERE: this ranks the risk of a FULL national internet SHUTDOWN (KeepItOn-style total blackout), NOT general censorship. A heavy-censorship country (e.g. AZ, AE, EG) correctly reads 'low' if it rarely does FULL shutdowns -- it can still be among the most censored (see /censorship-index and /v1/atlas). The blend weights the shutdown-validated structural model (v4) 0.6 over honest_forecast 0.4 BY DESIGN: honest_forecast is a broad censorship-anomaly signal that is noisy for shutdowns specifically (it scores e.g. Spain ~0.78), and the 0.6 structural weight was leave-one-country-out validated. So a country with high honest_forecast but low structural risk is correctly down-weighted here.
For the related but distinct OONI-anomaly forecast (different question), see /v1/forecast/honest/{country}.