voidly
voidly atlas · live model

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.

0.90
Cross-country AUC — predicting WHICH countries are at risk (Iran vs. Switzerland).
0.74
Within-country median AUC — predicting WHICH DAYS within a country (the real timing test).
13/23
Countries with usable timing skill (within-country AUC ≥ 0.65).

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.

Ground-truth labels

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 →
Measurement signals

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.

What this is — and isn't. This reliably ranks which countries are heading into danger, and that skill is validated forward in time: in a strict past→future holdout the cross-country ranking holds at AUC 0.87 (country-ranking 0.90, 7.8× better than chance). It does not forecast which day a shutdown hits inside a given country. The within-country figure (median 0.74) is cross-sectional — measured with the model having seen the whole period; forward in time it drops to ~0.36 (below chance), because shutdowns cluster in time. Treat this as a forward-validated country risk ranking, not a within-country calendar oracle. Full numbers in the live model card.
  1. 1MMelevated
    45.4%
    Country risk
    43.8%
    OONI signal
    74.7%
    As of
    2026-06-13
  2. 2RUelevated
    28.1%
    Country risk
    18.5%
    OONI signal
    80.0%
    As of
    2026-06-13
  3. 3PKelevated
    23.5%
    Country risk
    18.5%
    OONI signal
    73.4%
    As of
    2026-06-13
  4. 4INlow
    6.2%
    Country risk
    43.8%
    OONI signal
    3.0%
    As of
    2026-06-13
  5. 5IQlow
    6.2%
    Country risk
    5.7%
    OONI signal
    78.8%
    As of
    2026-06-13
  6. 6JOlow
    6.2%
    Country risk
    5.3%
    OONI signal
    63.0%
    As of
    2026-06-13
  7. 7TZlow
    6.2%
    Country risk
    7.0%
    OONI signal
    69.0%
    As of
    2026-06-13
  8. 8IRlow
    2.9%
    Country risk
    5.7%
    OONI signal
    22.2%
    As of
    2026-06-13
  9. 9SDlow
    2.9%
    Country risk
    1.9%
    OONI signal
    60.2%
    As of
    2026-06-10
  10. 10BYlow
    2.8%
    Country risk
    1.0%
    OONI signal
    75.4%
    As of
    2026-06-13
  11. 11ETlow
    1.8%
    Country risk
    1.0%
    OONI signal
    67.4%
    As of
    2026-06-13
  12. 12SYlow
    1.8%
    Country risk
    1.9%
    OONI signal
    49.1%
    As of
    2026-06-13
  13. 13KGlow
    1.5%
    Country risk
    1.0%
    OONI signal
    62.8%
    As of
    2026-06-13
  14. 14NGlow
    1.5%
    Country risk
    1.0%
    OONI signal
    63.8%
    As of
    2026-06-13
  15. 15OMlow
    1.5%
    Country risk
    1.0%
    OONI signal
    54.6%
    As of
    2026-06-13
  16. 16TMlow
    1.1%
    Country risk
    1.0%
    OONI signal
    46.8%
    As of
    2025-09-26
  17. 17VElow
    1.1%
    Country risk
    1.9%
    OONI signal
    23.8%
    As of
    2026-06-13
  18. 18AElow
    0.4%
    Country risk
    0.1%
    OONI signal
    65.0%
    As of
    2026-06-13
  19. 19AZlow
    0.4%
    Country risk
    0.1%
    OONI signal
    78.4%
    As of
    2026-06-13
  20. 20BFlow
    0.4%
    Country risk
    0.1%
    OONI signal
    80.3%
    As of
    2026-06-13
  21. 21BHlow
    0.4%
    Country risk
    0.1%
    OONI signal
    69.5%
    As of
    2026-06-11
  22. 22CNlow
    0.4%
    Country risk
    1.0%
    OONI signal
    1.9%
    As of
    2026-06-13
  23. 23CUlow
    0.4%
    Country risk
    0.1%
    OONI signal
    72.4%
    As of
    2026-06-13
  24. 24EGlow
    0.4%
    Country risk
    0.1%
    OONI signal
    87.0%
    As of
    2026-06-13
  25. 25ESlow
    0.4%
    Country risk
    0.1%
    OONI signal
    89.4%
    As of
    2026-06-13
  26. 26FIlow
    0.4%
    Country risk
    0.1%
    OONI signal
    40.9%
    As of
    2026-06-13
  27. 27GNlow
    0.4%
    Country risk
    1.0%
    OONI signal
    26.3%
    As of
    2026-06-13
  28. 28KElow
    0.4%
    Country risk
    1.9%
    OONI signal
    5.2%
    As of
    2026-06-13
  29. 29KZlow
    0.4%
    Country risk
    0.1%
    OONI signal
    72.5%
    As of
    2026-06-13
  30. 30LKlow
    0.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

Live from /v1/shutdown-risk/alerts. Sorted newest-first. Each row links to the per-country endpoint with its full validation embedded.

Push delivery (webhook)

One-click webhook subscribe — no account needed

#subscribe

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.

Must be https. Slack/Discord incoming webhooks work; so does any server endpoint that accepts JSON POST.

2-letter ISO code. Blank = receive crossings from every watched country.

Event types
We POST a test event to your URL first — if it doesn’t return 2xx, nothing is stored.

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).

474
Predictions archived
25
Flagged elevated
0
True positives so far
2025-09-26
Tracking since

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 — country risk
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": "..." }
Python — pull leaderboard daily
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']}")
Node — subscribe to the RSS alerts feed
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%)"
}
Voidly pushes to your webhook (no polling, no account)
# 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" }
Verify your wiring without waiting for a real crossing
# 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"
  }'
Verify the HMAC signature (Python receiver)
# 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)
Slack — pipe alerts into a channel
# 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_URL

Honest 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/&lcub;country&rcub;.