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

0.88
Cross-country AUC — predicting WHICH countries are at risk (Iran vs. Switzerland).
0.73
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(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.

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 60 countries sorted by 7-day shutdown probability.

What this is: a probability rank, not an onset guarantee. Cross-country AUC 0.91; within-country median 0.73 (validated against Access Now KeepItOn shutdowns). The model picks which countries are at risk well; the day-of-onset signal inside a single country is real but noisy. Read the honest onset-skill report for the metric breakdown.
  1. 1MMelevated
    56.6%
    Country risk
    45.8%
    OONI signal
    83.0%
    As of
    2026-05-16
  2. 2PKelevated
    22.6%
    Country risk
    19.6%
    OONI signal
    72.3%
    As of
    2026-05-16
  3. 3RUelevated
    22.6%
    Country risk
    19.6%
    OONI signal
    81.0%
    As of
    2026-05-16
  4. 4IQlow
    5.9%
    Country risk
    5.8%
    OONI signal
    67.6%
    As of
    2026-05-16
  5. 5JOlow
    5.9%
    Country risk
    5.6%
    OONI signal
    81.6%
    As of
    2026-05-16
  6. 6TZlow
    5.9%
    Country risk
    7.4%
    OONI signal
    74.5%
    As of
    2026-05-16
  7. 7IRlow
    3.1%
    Country risk
    5.8%
    OONI signal
    29.9%
    As of
    2026-05-16
  8. 8SYlow
    2.9%
    Country risk
    2.0%
    OONI signal
    47.9%
    As of
    2026-05-16
  9. 9BYlow
    2.4%
    Country risk
    1.0%
    OONI signal
    83.7%
    As of
    2026-05-16
  10. 10KGlow
    2.4%
    Country risk
    1.0%
    OONI signal
    83.1%
    As of
    2026-05-16
  11. 11ETlow
    2.0%
    Country risk
    1.0%
    OONI signal
    45.2%
    As of
    2026-05-16
  12. 12INlow
    2.0%
    Country risk
    45.8%
    OONI signal
    1.1%
    As of
    2026-05-16
  13. 13KElow
    2.0%
    Country risk
    2.0%
    OONI signal
    20.8%
    As of
    2026-05-16
  14. 14NGlow
    2.0%
    Country risk
    1.0%
    OONI signal
    64.1%
    As of
    2026-05-16
  15. 15OMlow
    2.0%
    Country risk
    1.0%
    OONI signal
    72.9%
    As of
    2026-05-16
  16. 16TGlow
    2.0%
    Country risk
    1.0%
    OONI signal
    30.9%
    As of
    2026-05-16
  17. 17VElow
    2.0%
    Country risk
    2.0%
    OONI signal
    15.3%
    As of
    2026-05-16
  18. 18GNlow
    0.9%
    Country risk
    1.0%
    OONI signal
    19.1%
    As of
    2026-05-16
  19. 19VNlow
    0.7%
    Country risk
    1.0%
    OONI signal
    12.7%
    As of
    2026-05-16
  20. 20AElow
    0.4%
    Country risk
    0.1%
    OONI signal
    78.3%
    As of
    2026-05-16
  21. 21AZlow
    0.4%
    Country risk
    0.1%
    OONI signal
    72.0%
    As of
    2026-05-16
  22. 22BFlow
    0.4%
    Country risk
    0.1%
    OONI signal
    68.3%
    As of
    2026-05-16
  23. 23BHlow
    0.4%
    Country risk
    0.1%
    OONI signal
    67.4%
    As of
    2026-05-16
  24. 24CHlow
    0.4%
    Country risk
    0.1%
    OONI signal
    28.7%
    As of
    2026-05-16
  25. 25CNlow
    0.4%
    Country risk
    1.0%
    OONI signal
    8.4%
    As of
    2026-05-16
  26. 26CUlow
    0.4%
    Country risk
    0.1%
    OONI signal
    31.7%
    As of
    2026-05-15
  27. 27EGlow
    0.4%
    Country risk
    0.1%
    OONI signal
    69.0%
    As of
    2026-05-16
  28. 28ESlow
    0.4%
    Country risk
    0.1%
    OONI signal
    59.8%
    As of
    2026-05-16
  29. 29FIlow
    0.4%
    Country risk
    0.1%
    OONI signal
    19.7%
    As of
    2026-05-16
  30. 30IDlow
    0.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

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

118
Predictions archived
5
Flagged elevated
0
True positives so far
2026-05-09
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

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