Most breach-monitoring setups die at step one: "create an account, generate an API key, add a payment method for when you exceed the free tier."
Here is one that starts at step zero. I maintain XposedOrNot, a free, open-source breach index, and the check endpoint is deliberately keyless:
curl "https://api.xposedornot.com/v1/check-email/you@example.com"
You get JSON back. No signup, no token, per-IP rate limits (documented in the API docs). That is the whole integration surface.
The 15-line nightly monitor
What I actually wanted was delta alerting on a handful of admin and service accounts: tell me only when the breach count for an address changes. That is a cron job, not a product:
#!/bin/bash
# breach-watch.sh: nightly delta-check of accounts you care about
# accounts.txt: one email per line, lives next to this script
cd "$(dirname "$0")" # cron runs from $HOME; keep state files next to the script
while read -r addr; do
body=$(curl -s "https://api.xposedornot.com/v1/check-email/${addr}")
hits=$(printf '%s' "$body" | jq -r '.breaches[0] | length' 2>/dev/null || echo 0)
known=$(cat ".known-${addr}" 2>/dev/null || echo 0)
if [ "${hits:-0}" -gt "${known}" ]; then
echo "${addr}: breach count now ${hits} (was ${known})" \
| mail -s "Breach alert: ${addr}" you@example.com
echo "${hits}" > ".known-${addr}"
fi
sleep 3 # stay inside the per-IP rate limit
done < accounts.txt
Notes from running this for real, because every one of these cost me something:
- Unknown addresses come back HTTP 200 with an
Errorbody, not a 404. jq'slengthon the missing key yields 0, so an address nobody has ever heard of simply never alerts. - Capture the body before you pipe it to jq. Run the loop twice in quick succession and you will meet the per-IP rate limit, the reply is not JSON, and jq would spray a parse error into your cron mail. The
2>/dev/null || echo 0fallback turns that into a quiet zero, which fails safe: a check that could not complete never invents an alert. -
sleep 3is not decoration either. It is what keeps a list of a dozen addresses inside that same limit. - First run alerts on every address that already has exposure. Treat that as your baseline, not a fire.
- State is one tiny file per address. Delete
.known-*to force a re-alert.
Drop it in cron at 03:00 and the only moving parts are curl, jq, and mail: standing breach visibility on the accounts you actually care about.
When you outgrow the script
Two honest limits, and what I use past them:
- The script sees one address at a time. For every address on a domain you own, verify the domain (DNS TXT, admin email, or an HTML file drop) and you get the org-wide view with email alerts, free, in about ten minutes: walkthrough here.
- Any index only knows about breaches that have surfaced. Across the 774 breaches in our catalog today, the median gap between a breach happening and it becoming publicly known is 1,591 days. That is more than four years of the exposure being real while nobody can tell you about it, which is the entire argument for standing monitoring over the occasional manual check. Nothing beats not reusing passwords.
If you want richer context than a count (exposure timeline, data categories), there is an analytics endpoint:
curl "https://api.xposedornot.com/v1/breach-analytics?email=you@example.com"
Audit it, do not trust it
The whole stack is MIT licensed on GitHub: github.com/XposedOrNot. API, site, SDKs. A monitoring tool you cannot read is just a vendor promise with extra steps.
If this saved you a signup form, a star on the repo genuinely helps others find it.
What does your team use for breach exposure today: something like this, a paid feed, or nothing yet? Curious what the gap looks like from the other side.
Top comments (0)