DEV Community

Alex Spinov
Alex Spinov

Posted on

Have I Been Pwned Has a Free API — Check If Any Email Was in a Data Breach

Why This Matters

Data breaches happen daily. Have I Been Pwned (HIBP) tracks 14+ billion compromised accounts across 800+ breaches. Their API lets you check programmatically — no manual lookups.

Check a Password (Without Sending It)

HIBP uses a k-anonymity model. You send only the first 5 characters of the SHA-1 hash. The API returns all matching hashes. Your password never leaves your machine.

import hashlib
import requests

def check_password(password):
    sha1 = hashlib.sha1(password.encode()).hexdigest().upper()
    prefix, suffix = sha1[:5], sha1[5:]

    r = requests.get(f"https://api.pwnedpasswords.com/range/{prefix}")

    for line in r.text.splitlines():
        hash_suffix, count = line.split(":")
        if hash_suffix == suffix:
            return int(count)
    return 0

count = check_password("password123")
if count:
    print(f"Found in {count:,} breaches! Change it immediately.")
else:
    print("Not found in any known breach.")
Enter fullscreen mode Exit fullscreen mode

Output: Found in 126,927 breaches! Change it immediately.

This is completely free, no API key needed, no rate limits.

Check an Email (API Key Required)

Email lookups need a free API key from haveibeenpwned.com:

def check_email(email, api_key):
    r = requests.get(
        f"https://haveibeenpwned.com/api/v3/breachedaccount/{email}",
        headers={"hibp-api-key": api_key},
        params={"truncateResponse": "false"}
    )
    if r.status_code == 200:
        breaches = r.json()
        return [{"name": b["Name"], "date": b["BreachDate"], 
                 "count": b["PwnCount"]} for b in breaches]
    elif r.status_code == 404:
        return []  # Not breached
    return None

breaches = check_email("test@example.com", "your_key")
for b in breaches or []:
    print(f"{b[date]}{b[name]} ({b[count]:,} accounts)")
Enter fullscreen mode Exit fullscreen mode

Real Use Cases

  1. Registration forms — warn users if their password is compromised
  2. Security audits — check employee emails against breach databases
  3. Password managers — flag reused compromised passwords
  4. Compliance — NIST SP 800-63B recommends checking passwords against breach lists
  5. Monitoring — set up alerts when your domain appears in new breaches

Batch Check for Your Domain

def check_domain(domain, api_key):
    r = requests.get(
        f"https://haveibeenpwned.com/api/v3/breaches",
        headers={"hibp-api-key": api_key}
    )
    breaches = r.json()
    # Filter breaches that might affect your domain
    recent = [b for b in breaches if b["AddedDate"] > "2025-01-01"]
    print(f"{len(recent)} breaches added in 2025+")
    for b in recent[:5]:
        print(f"  {b[Name]}: {b[PwnCount]:,} accounts ({b[BreachDate]})")
Enter fullscreen mode Exit fullscreen mode

Rate Limits

Endpoint Key Needed? Rate Limit
Password check No None (use responsibly)
Email check Yes ($3.50/mo) 10 req/min
Breach list No None
Paste search Yes 10 req/min

The password API is completely free and unlimited — use it in every registration form.

Add to Your Registration Form

// Frontend: check password on blur
async function isPasswordBreached(password) {
  const encoder = new TextEncoder();
  const data = encoder.encode(password);
  const hashBuffer = await crypto.subtle.digest("SHA-1", data);
  const hashArray = Array.from(new Uint8Array(hashBuffer));
  const sha1 = hashArray.map(b => b.toString(16).padStart(2, "0")).join("").toUpperCase();

  const prefix = sha1.slice(0, 5);
  const suffix = sha1.slice(5);

  const res = await fetch(`https://api.pwnedpasswords.com/range/${prefix}`);
  const text = await res.text();

  return text.includes(suffix);
}
Enter fullscreen mode Exit fullscreen mode

I write about APIs, security tools, and developer productivity. More on GitHub.


More from me: 10 Dev Tools I Use Daily | 77 Scrapers on a Schedule | 150+ Free APIs

Top comments (0)